Compare commits

...

93 Commits

Author SHA1 Message Date
Kovid Goyal
e06ef732d8 version 0.19.2 2020-11-13 09:32:53 +05:30
Kovid Goyal
792a3e7db8 Dont mix the raw/non-raw malloc/free for pagerhist 2020-11-13 08:12:26 +05:30
Kovid Goyal
9b32842f66 Fix #3096 2020-11-13 07:47:40 +05:30
Kovid Goyal
9816979169 Don't restrict the DCH control code to only the current scroll region
Matches behavior of other terminal emulators.  Fixes #3090
2020-11-12 11:44:09 +05:30
Kovid Goyal
a74679dd95 Fix last character of URL not being detected if it is the only character on a new line
Fixes #3088
2020-11-11 14:00:33 +05:30
Kovid Goyal
a40059729e Fix the send_text action not working in URL handlers
send_text keeps its args as byte string, so variable expansion was not
working. Fixes #3081
2020-11-11 07:51:43 +05:30
Kovid Goyal
6bab967586 Merge branch 'memory-leak' of https://github.com/Luflosi/kitty 2020-11-05 18:15:44 +05:30
Luflosi
bb88b33469 Fix memory leak
Found with the Clang Static Analyzer.
2020-11-05 13:15:19 +01:00
Kovid Goyal
c440470276 Entry point to edit config file 2020-11-04 16:30:29 +05:30
Kovid Goyal
ef6164eecb Merge branch 'incorrect-reference-count-decrement' of https://github.com/Luflosi/kitty 2020-11-04 07:44:00 +05:30
Luflosi
7ad360d872 Fix incorrect decrement of the reference counts of objects
Found with the Clang Static Analyzer.
The function `schedule_notification()` for the new notification API also does not `release` similar objects.
2020-11-03 18:17:58 +01:00
Kovid Goyal
0870a33956 Merge branch 'memory-leak' of https://github.com/Luflosi/kitty 2020-11-01 18:47:31 +05:30
Luflosi
3ccc2f538d Fix memory leak in unlikely failure condition
Found with the Clang Static Analyzer.
2020-11-01 13:41:52 +01:00
Kovid Goyal
17375f0be2 Add a link to nix-shell 2020-10-31 09:10:50 +05:30
Kovid Goyal
5a987a162e Merge branch 'add-shell.nix' of https://github.com/Luflosi/kitty 2020-10-31 07:31:19 +05:30
Luflosi
5058fb9919 Add shell.nix
This makes it possible to execute `nix-shell` to create the correct environment to build kitty. Use `nix-shell --pure` to eliminate most of the influence of the outside system, e.g. globally installed packages.
This works on NixOS and any Linux or macOS system with the Nix package manager installed.
The build inputs are split into `buildInputs`, `nativeBuildInputs`, `propagatedBuildInputs` and `checkInputs` so it closely resembles 2bb3a9da24/pkgs/applications/terminal-emulators/kitty/default.nix. This makes it easy to port changes between the two files.
2020-10-31 01:44:33 +01:00
Luflosi
3335f8602b Allow reading some build system flags as environment variables 2020-10-29 12:20:11 +01:00
Kovid Goyal
87ab1bbe54 Add a changelog entry for previous merge 2020-10-29 07:20:45 +05:30
Kovid Goyal
f8d52b33f4 Merge branch 'wl_fix_destroy_csds' of https://github.com/LeonardKoenig/kitty 2020-10-29 07:19:57 +05:30
Leonard König
bc2dcdf9db wl: Fix destroying CSDs in correct order (#3051)
On Wayland, some compositors refuse to implement SSDs in which case we draw
primitive CSDs.  However, the destructors were called in the wrong order,
leading to a dereference of an already freed object.
2020-10-28 18:57:37 +01:00
Kovid Goyal
93d1aacfb6 macOS: Fix kitty not being added to PATH automatically
Fixes #3063. Apparently in newer Python, Py_Initialize() nukes
sys._xoptions when the isolate flag is set.
2020-10-27 10:38:33 +05:30
Kovid Goyal
3ddaa4aff9 Make it clear that remote control mappings dont need remote control enabled 2020-10-26 16:51:53 +05:30
Kovid Goyal
75488d568a Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-10-26 16:34:30 +05:30
Luflosi
3df80545bd Reduce the difference of GLFW to upstream 2020-10-26 11:02:55 +01:00
Kovid Goyal
c605fd9783 Merge branch 'remove-extra-space' of https://github.com/Luflosi/kitty 2020-10-26 15:03:25 +05:30
Luflosi
60719e7bff Remove extra space 2020-10-26 10:22:14 +01:00
Kovid Goyal
e9faa3277a Merge branch 'fix-memory-leak' of https://github.com/Luflosi/kitty 2020-10-26 07:33:06 +05:30
Luflosi
35f7388725 Fix memory leaks in coretext_all_fonts()
Found with the Clang Static Analyzer.
2020-10-25 17:40:51 +01:00
Kovid Goyal
ef1486a779 Add .js and .json to known extensions 2020-10-25 20:25:31 +05:30
Kovid Goyal
95c44165e9 Merge branch 'fix-clang-analyze-error' of https://github.com/Luflosi/kitty 2020-10-25 20:24:28 +05:30
Kovid Goyal
7b816bb96f Font independent rendering for braille characters, which ensures they are properly aligned at all font sizes. 2020-10-25 20:20:22 +05:30
Luflosi
d2ac51aa4a Fix Clang Static Analyzer complaining about Apple coding conventions
The error message was: "Method accepting NSError** should have a non-void return value to indicate whether or not an error occurred".
2020-10-25 15:47:43 +01:00
Kovid Goyal
26974b1f3a Dont ignore --title when using an empty session
Fixes #3055
2020-10-25 18:53:57 +05:30
Kovid Goyal
3960c6678d Override MIME for some types that are actually text 2020-10-25 15:11:44 +05:30
Kovid Goyal
30b8991a73 DRYer 2020-10-25 13:47:12 +05:30
Kovid Goyal
75a94bcd96 Improving MIME type detection for some common file types when they are missing from the system MIME database
Also allow the user to specify their own database via mime.types in the
kitty config directory. See #3056
2020-10-25 13:42:11 +05:30
Kovid Goyal
e160cbf32b ssh kitten: Fix parsing of combined single letter options to more closely match ssh. Fixes #3053 2020-10-23 16:55:53 +05:30
Kovid Goyal
2da2e63245 Add another integration 2020-10-23 09:36:35 +05:30
Kovid Goyal
f160ea6012 Fix #3048 2020-10-21 21:57:35 +05:30
Kovid Goyal
8057bc639d Add diff context parser defs 2020-10-20 11:37:48 +05:30
Kovid Goyal
6f5ec5aa03 Merge branch 'fix-mistake-in-previous-pr' of https://github.com/Luflosi/kitty into master 2020-10-19 16:41:01 +05:30
Luflosi
e343c9445c Creating a temporary directory is not actually required
While developing the previous PR (https://github.com/kovidgoyal/kitty/pull/3043), I was using more compiler command line arguments, one of which was `-MMD`. This caused the .d file to be created. Without this argument, there is no need to create a temporary directory.
2020-10-19 13:08:32 +02:00
Kovid Goyal
c73e374f1a Remove unneeded space 2020-10-19 16:02:49 +05:30
Kovid Goyal
d9b292f808 Merge branch 'fix-macos-notification-api-detection' of https://github.com/Luflosi/kitty into master 2020-10-19 16:00:38 +05:30
Luflosi
46ef2f313b macOS: Fix detection for notification API
For some reason it can happen that the `UserNotifications` framework exists but the `UserNotifications/UserNotifications.h` header doesn't.
Closes https://github.com/kovidgoyal/kitty/issues/3042.
2020-10-19 12:19:51 +02:00
Luflosi
92a705c79e Add language parameter to first_successful_compile() and test_compile()
This allows compiling Objective-C files.
The temporary directory is needed because clang would try to write to `/dev/null.d`, which would obviously fail.
2020-10-18 23:08:19 +02:00
Kovid Goyal
12ebdd2574 Add a changelog entry for the last PR 2020-10-18 08:48:07 +05:30
Kovid Goyal
ed2e951032 Merge branch 'pointer_shape_when_dragging' of https://github.com/jaeheum/kitty into master 2020-10-18 08:46:18 +05:30
Kovid Goyal
01a34a8846 Add links to viu that supports the graphics protocol 2020-10-18 08:42:59 +05:30
Jay Han
b13174cb9c pointer_shape_when_dragging option + set_mouse_cursor_when_dragging() 2020-10-17 09:52:44 -07:00
Jay Han
03ac6cab99 default_pointer_shape option 2020-10-17 09:28:04 -07:00
Kovid Goyal
532eebaefb Merge branch 'fix-garbage-pointer-dereference' of https://github.com/Luflosi/kitty into master 2020-10-16 07:35:31 +05:30
Luflosi
f825345b14 Fix potential garbage pointer dereference
`create_os_window()` calls `get_window_content_scale()` without initializing the `xscale` and `yscale` variables. Instead of initializing them everywhere, where this function is called, just do it once in the function itself.
Found with the Clang Static Analyzer.
2020-10-15 20:41:16 +02:00
Kovid Goyal
1409477ed7 Merge branch 'double-free' of https://github.com/Luflosi/kitty into master 2020-10-15 19:11:31 +05:30
Luflosi
d8e4294432 Fix double free and memory leak
Found with the Clang Static Analyzer.
This is a copy & paste error. This bug was introduced in 72e2307c16 in Jul 2018.
2020-10-15 15:29:44 +02:00
Kovid Goyal
5d13908776 github actions CI updated to python 3.9
Pillow wheel currently not available for python 3.9. So tries to install
from source and fails on missing zlib. So install zlib in brew and make
it available via PKG_CONFIG_PATH. Apparently, this wont be necessary
after Pillow 8, but who knows.
2020-10-14 19:13:46 +05:30
Luflosi
2832a73285 GLFW: Wayland: Assume 96 DPI if physical size is <= 0
From upstream: 0ef149c8f2.
2020-10-14 14:43:40 +02:00
Kovid Goyal
4152ed5809 Update changelog 2020-10-14 08:51:07 +05:30
Kovid Goyal
8a90ed70ee Merge branch 'fix-upwards-selection-clearing' of https://github.com/Luflosi/kitty into master 2020-10-14 05:31:08 +05:30
Luflosi
e2646c08a5 Fix upwards selection not being cleared when lines change
When selecting text by dragging upwards, the "end" of the selection is above the "start". This causes the `selection_has_screen_line()` to never return `true` in this case.
In practice this bug causes the selection to not be cleared when the screen contents under the selection change if it was made by dragging upwards.
2020-10-13 21:40:03 +02:00
Kovid Goyal
7122ba820f Fix eight bar unicode chars not being rendered at very small font sizes
Fixes #3025
2020-10-13 20:00:25 +05:30
Kovid Goyal
efca2658ea Fix typing issues pointed out by updated mypy 2020-10-12 23:03:07 +05:30
Kovid Goyal
91a714947c Fix encoding error when reporting capabilities for number of colors 2020-10-12 22:39:05 +05:30
Kovid Goyal
5d3ac649ba Fix #3017 2020-10-08 17:39:44 +05:30
Kovid Goyal
6443e726a6 Add a note about how the query_terminal kitten works 2020-10-08 07:42:47 +05:30
Kovid Goyal
6a755bda45 A new kitten to easily query kitty for config values 2020-10-07 13:35:16 +05:30
Kovid Goyal
dab555ea3b kittens: Dont discard pending input on startup
Fixes #3012
2020-10-06 16:39:00 +05:30
Kovid Goyal
a3bbad0060 Allow specifying the optional actions for tcsetattr 2020-10-06 16:30:23 +05:30
Kovid Goyal
73db717a0c version 0.19.1 2020-10-06 14:03:25 +05:30
Kovid Goyal
f1552c8354 Avoid alias related complications with completion delegation for hyperlinked_grep 2020-10-06 13:53:41 +05:30
Kovid Goyal
fb87fc32f0 Fix a regression that caused a segfault when using scrollback_pager_history_size
Fixes #3011
2020-10-06 13:28:48 +05:30
Kovid Goyal
b04b0c670d Fix #3010
Probably ancient version of Python
2020-10-06 12:09:47 +05:30
Kovid Goyal
b33845df8a Update Changelog 2020-10-06 06:50:29 +05:30
Kovid Goyal
12c9d8d8f8 Delay load the ipaddress module 2020-10-06 06:50:17 +05:30
Kovid Goyal
fe97bbcbbf Merge branch 'master' of https://github.com/Sighery/kitty into master 2020-10-06 06:45:05 +05:30
Sighery
10533c3eba Hints kitten: validate IPs with ipaddress
On the initial commit of this feature, IPs were just matched with a
very simple regex that prioritised simplicity/readability over
accuracy.

This commit adds a postprocessor for ip matches that makes use of
Python's `ipaddress` in the standard library to validate all the IP
matches.

This way we don't need huge and complex regex patterns to match _and_
validate the IPs, and we can just use `ipaddress` to abstract us from
implementing all the validation logic into the regex pattern.
2020-10-05 20:11:25 +02:00
Sighery
b4415c90f9 Hints kitten: add support for IPs (v4 and v6)
It selects v4 and v6 IPs with a simple regex that doesn't actually
check for the validity of the IPs.
2020-10-05 20:11:22 +02:00
Kovid Goyal
2970bbdf6f Update GA tags
Also auto gen docs if running publish only for website
2020-10-05 20:04:32 +05:30
Kovid Goyal
95a420534d Remove the old asciidoc files
By now hopefully the kitty website is well established
2020-10-05 19:34:02 +05:30
Kovid Goyal
be56a21574 ... 2020-10-05 19:33:09 +05:30
Kovid Goyal
3741a235fe Merge branch 'patch-1' of https://github.com/Nudin/kitty into master 2020-10-05 19:28:22 +05:30
Michael F. Schönitzer
523b6c4c2d Fix wrong kittenname in documentation
It's called `hyperlinked_grep` not `hyperlink_grep`.
2020-10-05 15:54:11 +02:00
Kovid Goyal
c3b2300310 Merge branch 'remove-outdated-check' of https://github.com/Luflosi/kitty into master 2020-10-05 18:16:06 +05:30
Luflosi
1c4a20d86f Remove outdated Python version check
The Python version is already checked at the top of the file. That check was added in 81a58186c6.
2020-10-05 14:41:07 +02:00
Kovid Goyal
f5af475012 Merge branch 'notification_fix' of https://github.com/nertpinx/kitty into master 2020-10-05 18:09:35 +05:30
Martin Kletzander
f149b74332 Fix repeated version notifications
Without this fix the file with the list of notifications looks like this:
```
0.19.0,1601899026.4373078,<built-in method count of Notification object at 0x7f8c582193b0>
```
which ends up notifying about a new version all the time.
2020-10-05 14:27:47 +02:00
Kovid Goyal
8cd51386cb Fix some typos in the OSC 99 spec 2020-10-05 17:27:41 +05:30
Kovid Goyal
fb2cca88df Merge branch 'pip3-in-ci' of https://github.com/Luflosi/kitty into master 2020-10-04 21:23:29 +05:30
Luflosi
031d4dc85a Use pip3 instead of pip for CI
There was a warning when the CI executed the "macOS Brew" job:
```
DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support
```
To fix this, use pip3 instead of pip.
2020-10-04 17:47:41 +02:00
Kovid Goyal
bf66d1c0f5 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty into master 2020-10-04 21:13:48 +05:30
Luflosi
0edfa88755 Reduce the difference of GLFW to upstream 2020-10-04 17:34:51 +02:00
Kovid Goyal
dba1f3bcbc Merge branch 'add-comment' of https://github.com/Luflosi/kitty into master 2020-10-04 21:01:12 +05:30
Luflosi
e204cc8f12 Add comment on how to call send_test_notification() 2020-10-04 17:23:50 +02:00
68 changed files with 778 additions and 189 deletions

3
.gitattributes vendored
View File

@@ -11,3 +11,6 @@ kitty/parse-graphics-command.h linguist-generated=true
glfw/*.c linguist-vendored=true glfw/*.c linguist-vendored=true
glfw/*.h linguist-vendored=true glfw/*.h linguist-vendored=true
kittens/unicode_input/names.h linguist-generated=true kittens/unicode_input/names.h linguist-generated=true
*.py text diff=python
*.m text diff=objc

View File

@@ -40,7 +40,10 @@ def install_deps():
if is_bundle: if is_bundle:
install_bundle() install_bundle()
else: else:
run('pip install Pillow pygments') if is_macos:
# needed for zlib for pillow, should not be needed after pillow 8.0
os.environ['PKG_CONFIG_PATH'] = '/usr/local/opt/zlib/lib/pkgconfig'
run('pip3 install Pillow pygments')
def build_kitty(): def build_kitty():

View File

@@ -1,4 +1,5 @@
pkg-config pkg-config
zlib
python python
imagemagick imagemagick
harfbuzz harfbuzz

View File

@@ -65,6 +65,11 @@ def run_kitten(args: List[str]) -> None:
rk(kitten) rk(kitten)
def edit_config_file(args: List[str]) -> None:
from kitty.utils import edit_config_file as f
f()
def namespaced(args: List[str]) -> None: def namespaced(args: List[str]) -> None:
func = namespaced_entry_points[args[1]] func = namespaced_entry_points[args[1]]
func(args[1:]) func(args[1:])
@@ -77,6 +82,7 @@ entry_points = {
'runpy': runpy, 'runpy': runpy,
'launch': launch, 'launch': launch,
'kitten': run_kitten, 'kitten': run_kitten,
'edit-config': edit_config_file,
'@': remote_control, '@': remote_control,
'+': namespaced, '+': namespaced,

View File

@@ -78,6 +78,19 @@ you might have to rebuild the app.
a self signed certificate, see for example, `here a self signed certificate, see for example, `here
<https://stackoverflow.com/questions/27474751/how-can-i-codesign-an-app-without-being-in-the-mac-developer-program/27474942>`_. <https://stackoverflow.com/questions/27474751/how-can-i-codesign-an-app-without-being-in-the-mac-developer-program/27474942>`_.
Build and run from source with Nix
-------------------------------------------
On NixOS or any other Linux or macOS system with the Nix package manager
installed, execute `nix-shell
<https://nixos.org/guides/nix-pills/developing-with-nix-shell.html>`_ to create
the correct environment to build kitty or use ``nix-shell --pure`` instead to
eliminate most of the influence of the outside system, e.g. globally installed
packages. ``nix-shell`` will automatically fetch all required dependencies and
make them available in the newly spawned shell.
Then proceed with ``make`` or ``make app`` according to the platform specific instructions above.
Note for Linux/macOS packagers Note for Linux/macOS packagers
---------------------------------- ----------------------------------

View File

@@ -4,6 +4,61 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator. |kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
To update |kitty|, :doc:`follow the instructions <binary>`. To update |kitty|, :doc:`follow the instructions <binary>`.
0.19.2 [2020-11-13]
-------------------
- A new :doc:`kittens/query_terminal` kitten to easily query the running kitty
via escape codes to detect its version, and the values of
configuration options that enable or disable terminal features.
- Options to control mouse pointer shape, :opt:`default_pointer_shape`, and
:opt:`pointer_shape_when_dragging` (:pull:`3041`)
- Font independent rendering for braille characters, which ensures they are properly
aligned at all font sizes.
- Fix a regression in 0.19.0 that caused borders not to be drawn when setting
:opt:`window_margin_width` and keeping :opt:`draw_minimal_borders` on
(:iss:`3017`)
- Fix a regression in 0.19.0 that broke rendering of one-eight bar unicode
characters at very small font sizes (:iss:`3025`)
- Wayland: Fix a crash under GNOME when using multiple OS windows
(:pull:`3066`)
- Fix selections created by dragging upwards not being auto-cleared when
screen contents change (:pull:`3028`)
- macOS: Fix kitty not being added to PATH automatically when using pre-built
binaries (:iss:`3063`)
- Allow adding MIME definitions to kitty by placing a ``mime.types`` file in
the kitty config directory (:iss:`3056`)
- Dont ignore :option:`--title` when using a session file that defines no
windows (:iss:`3055`)
- Fix the send_text action not working in URL handlers (:iss:`3081`)
- Fix last character of URL not being detected if it is the only character on a
new line (:iss:`3088`)
- Don't restrict the ICH,DCH,REP control codes to only the current scroll region (:iss:`3090`, :iss:`3096`)
0.19.1 [2020-10-06]
-------------------
- hints kitten: Add an ``ip`` type for easy selection of IP addresses
(:pull:`3009`)
- Fix a regression that caused a segfault when using
:opt:`scrollback_pager_history_size` and it needs to be expanded (:iss:`3011`)
- Fix update available notifications repeating (:pull:`3006`)
0.19.0 [2020-10-04] 0.19.0 [2020-10-04]
------------------- -------------------

View File

@@ -165,9 +165,12 @@ kitty is not able to use my favorite font?
|kitty| achieves its stellar performance by caching alpha masks of each rendered |kitty| achieves its stellar performance by caching alpha masks of each rendered
character on the GPU, so that every character needs to be rendered only once. character on the GPU, so that every character needs to be rendered only once.
This means it is a strictly character cell based display. As such it can use This means it is a strictly character cell based display. As such it can use
only monospace fonts, since every cell in the grid has to be the same size. If only monospace fonts, since every cell in the grid has to be the same size.
your font is not listed in ``kitty list-fonts`` it means that it is not Furthermore, it needs fonts to be freely resizable, so it does not support
monospace. On Linux you can list all monospace fonts with:: bitmapped fonts.
If your font is not listed in ``kitty list-fonts`` it means that it is not
monospace or is a bitmapped font. On Linux you can list all monospace fonts with::
fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True

View File

@@ -37,6 +37,7 @@ Some programs that use the kitty graphics protocol:
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol * `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system * `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
information tool information tool
* `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer
.. contents:: .. contents::

View File

@@ -30,6 +30,10 @@ images and other types of documents directly in your terminal, even over SSH.
`koneko <https://github.com/twenty5151/koneko>`_ `koneko <https://github.com/twenty5151/koneko>`_
browse images from the pixiv artist community directly in kitty browse images from the pixiv artist community directly in kitty
`viu <https://github.com/atanunq/viu>`_
view images in the terminal, similar to kitty's icat, however also supports
displaying animated GIFs
System and data visualisation tools System and data visualisation tools
--------------------------------------- ---------------------------------------
@@ -63,6 +67,9 @@ System and data visualisation tools
iplot 'sin(x*3)*exp(x*.2)' iplot 'sin(x*3)*exp(x*.2)'
`onefetch <https://github.com/o2sh/onefetch>`_
a tool to fetch information about your git repositories
Editor integration Editor integration
----------------------- -----------------------

View File

@@ -34,7 +34,7 @@ you use some editor other than vim, you should adjust the
Finally, add an alias to your shell's rc files to invoke the kitten as ``hg``:: Finally, add an alias to your shell's rc files to invoke the kitten as ``hg``::
alias hg='kitty +kitten hyperlink_grep' alias hg='kitty +kitten hyperlinked_grep'
You can now run searches with:: You can now run searches with::
@@ -42,10 +42,17 @@ You can now run searches with::
hg some-search-term hg some-search-term
If you want to enable completion, for the kitten, you can delegate completion If you want to enable completion, for the kitten, you can delegate completion
to rg. For ZSH, you do that with:: to rg. For that, instead of using an alias create a simple wrapper script named
:file:`hg` somewhere in your ``PATH``:
compdef _rg kitty +kitten hyperlinked_grep .. code-block:: sh
#!/bin/sh
exec kitty +kitten hyperlinked_grep "$@"
Then, for example, for ZSH, add the following to :file:`.zshrc`::
compdef _rg hg
To learn more about kitty's powerful framework for customizing URL click To learn more about kitty's powerful framework for customizing URL click
actions, :doc:`see here <../open_actions>`. actions, :doc:`see here <../open_actions>`.

View File

@@ -0,0 +1,21 @@
Query terminal
=================
Used to query kitty from terminal programs about version, values of various
runtime options controlling its features, etc.
The querying is done using the (*semi*) standard XTGETTCAP escape sequence
pioneered by XTerm, so it works over SSH as well. The downside is that it
is slow, since it requires a roundtrip to the terminal emulator and back.
If you want to do some of the same querying in your terminal program without
depending on the kitten, you can do so, by processing the same escape codes.
Search `this page <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html>`_
for *XTGETTCAP* to see the syntax for the escape code and read the source
of this kitten to find the values of the keys for the various queries.
Command Line Interface
-------------------------
.. include:: ../generated/cli-kitten-query_terminal.rst

View File

@@ -81,7 +81,11 @@ lines. The various available criteria are:
``mime`` ``mime``
A comma separated list of MIME types, for example: ``text/*, image/*, A comma separated list of MIME types, for example: ``text/*, image/*,
application/pdf`` application/pdf``. You can add MIME types to kitty by creating the
:file:`mime.types` in the kitty configuration directory. Useful if your
system MIME database does not have definitions you need. This file is
in the standard format of one definition per line, like: ``text/plain rst
md``.
``ext`` ``ext``
A comma separated list of file extensions, for example: ``jpeg, tar.gz`` A comma separated list of file extensions, for example: ``jpeg, tar.gz``

View File

@@ -250,12 +250,13 @@ The escape code has the form::
<OSC> 99 ; metadata ; payload <terminator> <OSC> 99 ; metadata ; payload <terminator>
Here ``<OSC>`` is :code:`<ESC>]` and ``<terminator>`` is :code:`<ESC><backslash>`. The Here ``<OSC>`` is :code:`<ESC>]` and ``<terminator>`` is
metadata is a section of colon separated :code:`key=value` pairs. Every key :code:`<ESC><backslash>`. The metadata is a section of colon separated
must be a single character from the set :code:`[a-zA-Z]` and every value must :code:`key=value` pairs. Every key must be a single character from the set
be a character from the set :code:`a-zA-Z0-9-_/\+.,(){}[]*&^%$#@!`~`. The :code:`a-zA-Z` and every value must be a word consisting of characters from
payload must be interpreted based on the metadata section. The two semi-colons the set :code:`a-zA-Z0-9-_/\+.,(){}[]*&^%$#@!`~`. The payload must be
*must* always be present even when no metadata is present. interpreted based on the metadata section. The two semi-colons *must* always be
present even when no metadata is present.
Before going into details, lets see how one can display a simple, single line Before going into details, lets see how one can display a simple, single line
notification from a shell script:: notification from a shell script::
@@ -297,7 +298,7 @@ was activated. This is controlled by the ``a`` key which takes a comma
separated set of values, ``report`` and ``focus``. The value ``focus`` means separated set of values, ``report`` and ``focus``. The value ``focus`` means
focus the window from which the notification was issued and is the default. focus the window from which the notification was issued and is the default.
``report`` means send an escape code back to the application. The format of the ``report`` means send an escape code back to the application. The format of the
escape code is:: returned escape code is::
<OSC> 99 ; i=identifier ; <terminator> <OSC> 99 ; i=identifier ; <terminator>
@@ -322,7 +323,7 @@ to display it based on what it does understand.
allow displaying custom icons on a notification, at all, it was decided to allow displaying custom icons on a notification, at all, it was decided to
leave it out of the spec for the time being. leave it out of the spec for the time being.
Similarly, features such a scheduled notifications could be added in future Similarly, features such as scheduled notifications could be added in future
revisions. revisions.

View File

@@ -144,7 +144,8 @@ you can map it in :file:`kitty.conf`. For example::
Then pressing the :kbd:`F1` key will set the active window margins to 30. Then pressing the :kbd:`F1` key will set the active window margins to 30.
The syntax for what follows :code:`remote_control` is exactly the same The syntax for what follows :code:`remote_control` is exactly the same
as the syntax for what follows :code:`kitty @` above. as the syntax for what follows :code:`kitty @` above. You do not need
to enable remote control to use these mappings.
Documentation for the remote control protocol Documentation for the remote control protocol

View File

@@ -529,7 +529,6 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
*width = (int)frameRect.size.width; *width = (int)frameRect.size.width;
if (height) if (height)
*height = (int)frameRect.size.height; *height = (int)frameRect.size.height;
} }
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count) GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)

View File

@@ -1595,10 +1595,10 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
{ {
if (numer != GLFW_DONT_CARE && denom != GLFW_DONT_CARE) if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE)
[window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
else
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)]; [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
else
[window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
} }
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr) void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr)
@@ -1994,8 +1994,10 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
bytesPerRow:src->width * 4 bytesPerRow:src->width * 4
bitsPerPixel:32]; bitsPerPixel:32];
if (rep == nil) if (rep == nil) {
[native release];
return false; return false;
}
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4); memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
[native addRepresentation:rep]; [native addRepresentation:rep];
@@ -2446,7 +2448,7 @@ START_ALLOW_CASE_RANGE
END_ALLOW_CASE_RANGE END_ALLOW_CASE_RANGE
} }
if (utf_16_key != 0) { if (utf_16_key != 0) {
strncpy(cocoa_key, [[NSString stringWithCharacters:&utf_16_key length:1] UTF8String], key_sz - 1); strncpy(cocoa_key, [[NSString stringWithCharacters:&utf_16_key length:1] UTF8String], key_sz - 1);
} else { } else {
unsigned str_pos = 0; unsigned str_pos = 0;
for (unsigned i = 0; i < 4 && str_pos < key_sz - 1; i++) { for (unsigned i = 0; i < 4 && str_pos < key_sz - 1; i++) {

3
glfw/memfd.h vendored
View File

@@ -39,8 +39,7 @@ static inline int memfd_create(const char *name, unsigned int flags) {
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
static inline int static inline int createTmpfileCloexec(char* tmpname)
createTmpfileCloexec(char* tmpname)
{ {
int fd; int fd;

14
glfw/wl_init.c vendored
View File

@@ -717,6 +717,9 @@ glfwWaylandCheckForServerSideDecorations(void) {
int _glfwPlatformInit(void) int _glfwPlatformInit(void)
{ {
int i;
_GLFWmonitor* monitor;
_glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0"); _glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0");
if (!_glfw.wl.cursor.handle) if (!_glfw.wl.cursor.handle)
{ {
@@ -768,6 +771,17 @@ int _glfwPlatformInit(void)
// Sync so we got all initial output events // Sync so we got all initial output events
wl_display_roundtrip(_glfw.wl.display); wl_display_roundtrip(_glfw.wl.display);
for (i = 0; i < _glfw.monitorCount; ++i)
{
monitor = _glfw.monitors[i];
if (monitor->widthMM <= 0 || monitor->heightMM <= 0)
{
// If Wayland does not provide a physical size, assume the default 96 DPI
monitor->widthMM = (int) (monitor->modes[monitor->wl.currentMode].width * 25.4f / 96.f);
monitor->heightMM = (int) (monitor->modes[monitor->wl.currentMode].height * 25.4f / 96.f);
}
}
if (!_glfw.wl.wmBase) if (!_glfw.wl.wmBase)
{ {
_glfwInputError(GLFW_PLATFORM_ERROR, _glfwInputError(GLFW_PLATFORM_ERROR,

8
glfw/wl_platform.h vendored
View File

@@ -204,14 +204,14 @@ typedef struct _GLFWlibraryWayland
struct wl_seat* seat; struct wl_seat* seat;
struct wl_pointer* pointer; struct wl_pointer* pointer;
struct wl_keyboard* keyboard; struct wl_keyboard* keyboard;
struct wl_data_device_manager* dataDeviceManager;
struct wl_data_device* dataDevice;
struct xdg_wm_base* wmBase; struct xdg_wm_base* wmBase;
struct zxdg_decoration_manager_v1* decorationManager; struct zxdg_decoration_manager_v1* decorationManager;
struct wp_viewporter* viewporter; struct wp_viewporter* viewporter;
struct zwp_relative_pointer_manager_v1* relativePointerManager; struct zwp_relative_pointer_manager_v1* relativePointerManager;
struct zwp_pointer_constraints_v1* pointerConstraints; struct zwp_pointer_constraints_v1* pointerConstraints;
struct zwp_idle_inhibit_manager_v1* idleInhibitManager; struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
struct wl_data_device_manager* dataDeviceManager;
struct wl_data_device* dataDevice;
struct wl_data_source* dataSourceForClipboard; struct wl_data_source* dataSourceForClipboard;
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager; struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
struct zwp_primary_selection_device_v1* primarySelectionDevice; struct zwp_primary_selection_device_v1* primarySelectionDevice;
@@ -226,6 +226,7 @@ typedef struct _GLFWlibraryWayland
int32_t keyboardRepeatRate; int32_t keyboardRepeatRate;
monotonic_t keyboardRepeatDelay; monotonic_t keyboardRepeatDelay;
struct { struct {
uint32_t key; uint32_t key;
id_type keyRepeatTimer; id_type keyRepeatTimer;
@@ -275,7 +276,8 @@ typedef struct _GLFWmonitorWayland
int x; int x;
int y; int y;
int scale; int scale;
} _GLFWmonitorWayland;
} _GLFWmonitorWayland;
// Wayland-specific per-cursor data // Wayland-specific per-cursor data
// //

23
glfw/wl_window.c vendored
View File

@@ -42,8 +42,7 @@
#include <sys/mman.h> #include <sys/mman.h>
static void static void setCursorImage(_GLFWwindow* window)
setCursorImage(_GLFWwindow* window)
{ {
_GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR}; _GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR};
_GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor; _GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor;
@@ -247,8 +246,7 @@ static void dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, in
* is set to ENOSPC. If posix_fallocate() is not supported, program may * is set to ENOSPC. If posix_fallocate() is not supported, program may
* receive SIGBUS on accessing mmap()'ed file contents instead. * receive SIGBUS on accessing mmap()'ed file contents instead.
*/ */
static int static int createAnonymousFile(off_t size)
createAnonymousFile(off_t size)
{ {
int ret, fd = -1, shm_anon = 0; int ret, fd = -1, shm_anon = 0;
#ifdef HAS_MEMFD_CREATE #ifdef HAS_MEMFD_CREATE
@@ -415,10 +413,10 @@ static void createDecorations(_GLFWwindow* window)
static void destroyDecoration(_GLFWdecorationWayland* decoration) static void destroyDecoration(_GLFWdecorationWayland* decoration)
{ {
if (decoration->surface)
wl_surface_destroy(decoration->surface);
if (decoration->subsurface) if (decoration->subsurface)
wl_subsurface_destroy(decoration->subsurface); wl_subsurface_destroy(decoration->subsurface);
if (decoration->surface)
wl_surface_destroy(decoration->surface);
if (decoration->viewport) if (decoration->viewport)
wp_viewport_destroy(decoration->viewport); wp_viewport_destroy(decoration->viewport);
decoration->surface = NULL; decoration->surface = NULL;
@@ -439,7 +437,9 @@ static void xdgDecorationHandleConfigure(void* data,
uint32_t mode) uint32_t mode)
{ {
_GLFWwindow* window = data; _GLFWwindow* window = data;
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
if (!window->wl.decorations.serverSide) if (!window->wl.decorations.serverSide)
createDecorations(window); createDecorations(window);
} }
@@ -547,8 +547,7 @@ static bool createSurface(_GLFWwindow* window,
return true; return true;
} }
static void static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
{ {
if (window->wl.xdg.toplevel) if (window->wl.xdg.toplevel)
{ {
@@ -739,8 +738,7 @@ static bool createXdgSurface(_GLFWwindow* window)
return true; return true;
} }
static void static void incrementCursorImage(_GLFWwindow* window)
incrementCursorImage(_GLFWwindow* window)
{ {
if (window && window->wl.decorations.focus == mainWindow && window->cursorMode != GLFW_CURSOR_HIDDEN) { if (window && window->wl.decorations.focus == mainWindow && window->cursorMode != GLFW_CURSOR_HIDDEN) {
_GLFWcursor* cursor = window->wl.currentCursor; _GLFWcursor* cursor = window->wl.currentCursor;
@@ -788,8 +786,7 @@ wayland_read_events(int poll_result, int events, void *data UNUSED) {
else wl_display_cancel_read(_glfw.wl.display); else wl_display_cancel_read(_glfw.wl.display);
} }
static void static void handleEvents(monotonic_t timeout)
handleEvents(monotonic_t timeout)
{ {
struct wl_display* display = _glfw.wl.display; struct wl_display* display = _glfw.wl.display;
errno = 0; errno = 0;
@@ -958,7 +955,6 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
window->context.destroy(window); window->context.destroy(window);
destroyDecorations(window); destroyDecorations(window);
if (window->wl.xdg.decoration) if (window->wl.xdg.decoration)
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration); zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
@@ -1362,6 +1358,7 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
cursor->wl.buffer = createShmBuffer(image); cursor->wl.buffer = createShmBuffer(image);
if (!cursor->wl.buffer) if (!cursor->wl.buffer)
return false; return false;
cursor->wl.width = image->width; cursor->wl.width = image->width;
cursor->wl.height = image->height; cursor->wl.height = image->height;
cursor->wl.xhot = xhot; cursor->wl.xhot = xhot;

6
glfw/x11_window.c vendored
View File

@@ -423,14 +423,16 @@ static char* convertLatin1toUTF8(const char* source)
const char* sp; const char* sp;
if (source) { if (source) {
for (sp = source; *sp; sp++) size += (*sp & 0x80) ? 2 : 1; for (sp = source; *sp; sp++)
size += (*sp & 0x80) ? 2 : 1;
} }
char* target = calloc(size, 1); char* target = calloc(size, 1);
char* tp = target; char* tp = target;
if (source) { if (source) {
for (sp = source; *sp; sp++) tp += encodeUTF8(tp, *sp); for (sp = source; *sp; sp++)
tp += encodeUTF8(tp, *sp);
} }
return target; return target;

View File

@@ -1 +0,0 @@
See https://sw.kovidgoyal.net/kitty/graphics-protocol.html

View File

@@ -1 +0,0 @@
See https://sw.kovidgoyal.net/kitty/key-encoding.html

View File

@@ -7,7 +7,7 @@ import re
from contextlib import suppress from contextlib import suppress
from functools import lru_cache from functools import lru_cache
from hashlib import md5 from hashlib import md5
from mimetypes import guess_type from kitty.guess_mime_type import guess_type
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Iterator, Tuple, Union from typing import TYPE_CHECKING, Dict, List, Set, Optional, Iterator, Tuple, Union
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -68,7 +68,10 @@ class Collection:
self.removed_count += len(lines_for_path(left_path)) self.removed_count += len(lines_for_path(left_path))
def finalize(self) -> None: def finalize(self) -> None:
self.all_paths.sort(key=path_name_map.get) def key(x: str) -> str:
return path_name_map.get(x, '')
self.all_paths.sort(key=key)
def __iter__(self) -> Iterator[Tuple[str, str, Optional[str]]]: def __iter__(self) -> Iterator[Tuple[str, str, Optional[str]]]:
for path in self.all_paths: for path in self.all_paths:
@@ -133,7 +136,7 @@ def sanitize(text: str) -> str:
@lru_cache(maxsize=1024) @lru_cache(maxsize=1024)
def mime_type_for_path(path: str) -> str: def mime_type_for_path(path: str) -> str:
return guess_type(path)[0] or 'application/octet-stream' return guess_type(path) or 'application/octet-stream'
@lru_cache(maxsize=1024) @lru_cache(maxsize=1024)

View File

@@ -13,7 +13,9 @@ from collections import defaultdict
from contextlib import suppress from contextlib import suppress
from functools import partial from functools import partial
from gettext import gettext as _ from gettext import gettext as _
from typing import DefaultDict, Dict, Iterable, List, Optional, Tuple, Union from typing import (
Any, DefaultDict, Dict, Iterable, List, Optional, Tuple, Union
)
from kitty.cli import CONFIG_HELP, parse_args from kitty.cli import CONFIG_HELP, parse_args
from kitty.cli_stub import DiffCLIOptions from kitty.cli_stub import DiffCLIOptions
@@ -24,6 +26,11 @@ from kitty.key_encoding import RELEASE, KeyEvent, enter_key, key_defs as K
from kitty.options_stub import DiffOptions from kitty.options_stub import DiffOptions
from kitty.utils import ScreenSize from kitty.utils import ScreenSize
from ..tui.handler import Handler
from ..tui.images import ImageManager, Placement
from ..tui.line_edit import LineEdit
from ..tui.loop import Loop
from ..tui.operations import styled
from . import global_data from . import global_data
from .collect import ( from .collect import (
Collection, create_collection, data_for_path, lines_for_path, sanitize, Collection, create_collection, data_for_path, lines_for_path, sanitize,
@@ -35,14 +42,11 @@ from .render import (
ImagePlacement, ImageSupportWarning, Line, LineRef, Reference, render_diff ImagePlacement, ImageSupportWarning, Line, LineRef, Reference, render_diff
) )
from .search import BadRegex, Search from .search import BadRegex, Search
from ..tui.handler import Handler
from ..tui.images import ImageManager, Placement
from ..tui.line_edit import LineEdit
from ..tui.loop import Loop
from ..tui.operations import styled
try: try:
from .highlight import initialize_highlighter, highlight_collection, DiffHighlight from .highlight import (
DiffHighlight, highlight_collection, initialize_highlighter
)
has_highlighter = True has_highlighter = True
DiffHighlight DiffHighlight
except ImportError: except ImportError:
@@ -566,8 +570,8 @@ class ShowWarning:
def __init__(self) -> None: def __init__(self) -> None:
self.warnings: List[str] = [] self.warnings: List[str] = []
def __call__(self, message: str, category: object, filename: str, lineno: int, file: object = None, line: object = None) -> None: def __call__(self, message: Any, category: Any, filename: str, lineno: int, file: object = None, line: object = None) -> None:
if category is ImageSupportWarning: if category is ImageSupportWarning and isinstance(message, str):
showwarning.warnings.append(message) showwarning.warnings.append(message)

View File

@@ -236,6 +236,11 @@ def postprocessor(func: PostprocessorFunc) -> PostprocessorFunc:
return func return func
class InvalidMatch(Exception):
"""Raised when a match turns out to be invalid."""
pass
@postprocessor @postprocessor
def url(text: str, s: int, e: int) -> Tuple[int, int]: def url(text: str, s: int, e: int) -> Tuple[int, int]:
if s > 4 and text[s - 5:s] == 'link:': # asciidoc URLs if s > 4 and text[s - 5:s] == 'link:': # asciidoc URLs
@@ -280,11 +285,29 @@ def quotes(text: str, s: int, e: int) -> Tuple[int, int]:
return s, e return s, e
@postprocessor
def ip(text: str, s: int, e: int) -> Tuple[int, int]:
from ipaddress import ip_address
# Check validity of IPs (or raise InvalidMatch)
ip = text[s:e]
try:
ip_address(ip)
except Exception:
raise InvalidMatch("Invalid IP")
return s, e
def mark(pattern: str, post_processors: Iterable[PostprocessorFunc], text: str, args: HintsCLIOptions) -> Generator[Mark, None, None]: def mark(pattern: str, post_processors: Iterable[PostprocessorFunc], text: str, args: HintsCLIOptions) -> Generator[Mark, None, None]:
pat = re.compile(pattern) pat = re.compile(pattern)
for idx, (s, e, groupdict) in enumerate(regex_finditer(pat, args.minimum_match_length, text)): for idx, (s, e, groupdict) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
for func in post_processors: try:
s, e = func(text, s, e) for func in post_processors:
s, e = func(text, s, e)
except InvalidMatch:
continue
mark_text = text[s:e].replace('\n', '').replace('\0', '') mark_text = text[s:e].replace('\n', '').replace('\0', '')
yield Mark(idx, s, e, mark_text, groupdict) yield Mark(idx, s, e, mark_text, groupdict)
@@ -325,6 +348,15 @@ def functions_for(args: HintsCLIOptions) -> Tuple[str, List[PostprocessorFunc]]:
pattern = '(?m)^\\s*(.+)[\\s\0]*$' pattern = '(?m)^\\s*(.+)[\\s\0]*$'
elif args.type == 'hash': elif args.type == 'hash':
pattern = '[0-9a-f]{7,128}' pattern = '[0-9a-f]{7,128}'
elif args.type == 'ip':
pattern = (
# # IPv4 with no validation
r"((?:\d{1,3}\.){3}\d{1,3}"
r"|"
# # IPv6 with no validation
r"(?:[a-fA-F0-9]{0,4}:){2,7}[a-fA-F0-9]{1,4})"
)
post_processors.append(ip)
elif args.type == 'word': elif args.type == 'word':
chars = args.word_characters chars = args.word_characters
if chars is None: if chars is None:
@@ -402,7 +434,7 @@ def process_hyperlinks(text: str) -> Tuple[str, Tuple[Mark, ...]]:
active_hyperlink_start_offset = 0 active_hyperlink_start_offset = 0
idx += 1 idx += 1
def process_hyperlink(m: re.Match) -> str: def process_hyperlink(m: 're.Match') -> str:
nonlocal removed_size, active_hyperlink_url, active_hyperlink_id, active_hyperlink_start_offset nonlocal removed_size, active_hyperlink_url, active_hyperlink_id, active_hyperlink_start_offset
raw = m.group() raw = m.group()
start = m.start() - removed_size start = m.start() - removed_size
@@ -482,7 +514,7 @@ programs.
--type --type
default=url default=url
choices=url,regex,path,line,hash,word,linenum,hyperlink choices=url,regex,path,line,hash,word,linenum,hyperlink,ip
The type of text to search for. A value of :code:`linenum` is special, it looks The type of text to search for. A value of :code:`linenum` is special, it looks
for error messages using the pattern specified with :option:`--regex`, which for error messages using the pattern specified with :option:`--regex`, which
must have the named groups, :code:`path` and :code:`line`. If not specified, must have the named groups, :code:`path` and :code:`line`. If not specified,

View File

@@ -3,7 +3,6 @@
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import contextlib import contextlib
import mimetypes
import os import os
import re import re
import socket import socket
@@ -18,6 +17,7 @@ from typing import (
Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
) )
from kitty.guess_mime_type import guess_type
from kitty.cli import parse_args from kitty.cli import parse_args
from kitty.cli_stub import IcatCLIOptions from kitty.cli_stub import IcatCLIOptions
from kitty.constants import appname from kitty.constants import appname
@@ -273,7 +273,7 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
def scan(d: str) -> Generator[Tuple[str, str], None, None]: def scan(d: str) -> Generator[Tuple[str, str], None, None]:
for dirpath, dirnames, filenames in os.walk(d): for dirpath, dirnames, filenames in os.walk(d):
for f in filenames: for f in filenames:
mt = mimetypes.guess_type(f)[0] mt = guess_type(f)
if mt and mt.startswith('image/'): if mt and mt.startswith('image/'):
yield os.path.join(dirpath, f), mt yield os.path.join(dirpath, f), mt

View File

View File

@@ -0,0 +1,162 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import re
import sys
from binascii import unhexlify, hexlify
from contextlib import suppress
from typing import Dict, Iterable, List, Type
from kitty.cli import parse_args
from kitty.cli_stub import QueryTerminalCLIOptions
from kitty.constants import appname
from kitty.utils import TTYIO
from kitty.terminfo import names
class Query:
name: str = ''
ans: str = ''
query_name: str = ''
help_text: str = ''
def __init__(self) -> None:
self.encoded_query_name = hexlify(self.query_name.encode('utf-8')).decode('ascii')
self.pat = re.compile('\x1bP([01])\\+r{}(.*?)\x1b\\\\'.format(self.encoded_query_name).encode('ascii'))
def query_code(self) -> str:
return "\x1bP+q{}\x1b\\".format(self.encoded_query_name)
def decode_response(self, res: bytes) -> str:
return unhexlify(res).decode('utf-8')
def more_needed(self, buffer: bytes) -> bool:
m = self.pat.search(buffer)
if m is None:
return True
if m.group(1) == b'1':
q = m.group(2)
if q.startswith(b'='):
with suppress(Exception):
self.ans = self.decode_response(memoryview(q)[1:])
return False
def output_line(self) -> str:
return self.ans
all_queries: Dict[str, Type[Query]] = {}
def query(cls: Type[Query]) -> Type[Query]:
all_queries[cls.name] = cls
return cls
@query
class TerminalName(Query):
name: str = 'name'
query_name: str = 'TN'
help_text: str = f'Terminal name ({names[0]})'
@query
class TerminalVersion(Query):
name: str = 'version'
query_name: str = 'kitty-query-version'
help_text: str = 'Terminal version, for e.g.: 0.19.2'
@query
class AllowHyperlinks(Query):
name: str = 'allow_hyperlinks'
query_name: str = 'kitty-query-allow_hyperlinks'
help_text: str = 'yes, no or ask'
def do_queries(queries: Iterable, cli_opts: QueryTerminalCLIOptions) -> Dict[str, str]:
actions = tuple(all_queries[x]() for x in queries)
qstring = ''.join(a.query_code() for a in actions)
received = b''
def more_needed(data: bytes) -> bool:
nonlocal received
received += data
for a in actions:
if a.more_needed(received):
return True
return False
with TTYIO() as ttyio:
ttyio.send(qstring)
ttyio.recv(more_needed, timeout=cli_opts.wait_for)
return {a.name: a.output_line() for a in actions}
def options_spec() -> str:
return '''\
--wait-for
type=float
default=10
The amount of time (in seconds) to wait for a response from the terminal, after
querying it.
'''
help_text = '''\
Query the terminal this kitten is run in for various
capabilities. This sends escape codes to the terminal
and based on its response prints out data about supported
capabilities. Note that this is a blocking operation, since
it has to wait for a response from the terminal. You can control
the maximum wait time via the ``--wait-for`` option.
The output is lines of the form::
query: data
If a particular query is unsupported by the running kitty version,
the data will be blank.
Note that when calling this from another program, be very
careful not to perform any I/O on the terminal device
until the kitten exits.
Available queries are::
{}
'''.format(' ' + '\n '.join(
f'{name}: {c.help_text}' for name, c in all_queries.items()))
usage = '[query1 query2 ...]'
def main(args: List[str] = sys.argv) -> None:
cli_opts, items_ = parse_args(
args[1:],
options_spec,
usage,
help_text,
'{} +kitten query_terminal'.format(appname),
result_class=QueryTerminalCLIOptions
)
queries: List[str] = list(items_)
if 'all' in queries or not queries:
queries = sorted(all_queries)
else:
extra = frozenset(queries) - frozenset(all_queries)
if extra:
raise SystemExit(f'Unknown queries: {", ".join(extra)}')
for key, val in do_queries(queries, cli_opts).items():
print(key + ':', val)
if __name__ == '__main__':
main()
elif __name__ == '__doc__':
cd = sys.cli_docs # type: ignore
cd['usage'] = usage
cd['options'] = options_spec
cd['help_text'] = help_text

View File

@@ -118,15 +118,14 @@ def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
if arg in boolean_ssh_args: if arg in boolean_ssh_args:
ssh_args.append(arg) ssh_args.append(arg)
continue continue
if arg.startswith('-p') and arg[2:].isdigit():
ssh_args.append(arg)
continue
if arg in other_ssh_args: if arg in other_ssh_args:
if i != len(all_args) - 1:
raise SystemExit('Option {} cannot occur in the middle'.format(arg))
ssh_args.append(arg) ssh_args.append(arg)
expecting_option_val = True rest = all_args[i+1:]
continue if rest:
ssh_args.append(rest)
else:
expecting_option_val = True
break
raise SystemExit('Unknown option: {}'.format(arg)) raise SystemExit('Unknown option: {}'.format(arg))
continue continue
if expecting_option_val: if expecting_option_val:

View File

@@ -10,6 +10,7 @@ import re
import selectors import selectors
import signal import signal
import sys import sys
import termios
from contextlib import contextmanager from contextlib import contextmanager
from functools import partial from functools import partial
from typing import Any, Callable, Dict, Generator, List, NamedTuple, Optional from typing import Any, Callable, Dict, Generator, List, NamedTuple, Optional
@@ -61,8 +62,9 @@ debug = Debug()
class TermManager: class TermManager:
def __init__(self) -> None: def __init__(self, optional_actions: int = termios.TCSANOW) -> None:
self.extra_finalize: Optional[str] = None self.extra_finalize: Optional[str] = None
self.optional_actions = optional_actions
def set_state_for_loop(self, set_raw: bool = True) -> None: def set_state_for_loop(self, set_raw: bool = True) -> None:
if set_raw: if set_raw:
@@ -82,7 +84,7 @@ class TermManager:
self.set_state_for_loop() self.set_state_for_loop()
def __enter__(self) -> 'TermManager': def __enter__(self) -> 'TermManager':
self.tty_fd, self.original_termios = open_tty() self.tty_fd, self.original_termios = open_tty(False, self.optional_actions)
self.set_state_for_loop(set_raw=False) self.set_state_for_loop(set_raw=False)
return self return self
@@ -183,7 +185,8 @@ class Loop:
def __init__( def __init__(
self, self,
sanitize_bracketed_paste: str = '[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]' sanitize_bracketed_paste: str = '[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]',
optional_actions: int = termios.TCSADRAIN
): ):
if is_macos: if is_macos:
# On macOS PTY devices are not supported by the KqueueSelector and # On macOS PTY devices are not supported by the KqueueSelector and
@@ -193,6 +196,7 @@ class Loop:
else: else:
self.asycio_loop = asyncio.get_event_loop() self.asycio_loop = asyncio.get_event_loop()
self.return_code = 0 self.return_code = 0
self.optional_actions = optional_actions
self.read_buf = '' self.read_buf = ''
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore') self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
try: try:
@@ -388,7 +392,7 @@ class Loop:
handler.on_resize(handler.screen_size) handler.on_resize(handler.screen_size)
signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term) signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term)
with TermManager() as term_manager, signal_manager: with TermManager(self.optional_actions) as term_manager, signal_manager:
self._get_screen_size: ScreenSizeGetter = screen_size_function(term_manager.tty_fd) self._get_screen_size: ScreenSizeGetter = screen_size_function(term_manager.tty_fd)
image_manager = None image_manager = None
if handler.image_manager_class is not None: if handler.image_manager_class is not None:

View File

@@ -69,7 +69,7 @@ class Borders:
self.os_window_id = os_window_id self.os_window_id = os_window_id
self.tab_id = tab_id self.tab_id = tab_id
self.draw_active_borders = opts.active_border_color is not None self.draw_active_borders = opts.active_border_color is not None
self.draw_minimal_borders = opts.draw_minimal_borders self.draw_minimal_borders = opts.draw_minimal_borders and max(opts.window_margin_width) < 1
def __call__( def __call__(
self, self,

View File

@@ -1563,6 +1563,7 @@ class Boss:
for os_window_id in os_windows: for os_window_id in os_windows:
self.default_bg_changed_for(os_window_id) self.default_bg_changed_for(os_window_id)
# Can be called with kitty -o "map f1 send_test_notification"
def send_test_notification(self) -> None: def send_test_notification(self) -> None:
from time import monotonic from time import monotonic

View File

@@ -13,6 +13,7 @@ class CLIOptions:
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
QueryTerminalCLIOptions = CLIOptions
def generate_stub() -> None: def generate_stub() -> None:
@@ -50,6 +51,9 @@ def generate_stub() -> None:
from kittens.icat.main import options_spec from kittens.icat.main import options_spec
do(options_spec(), 'IcatCLIOptions') do(options_spec(), 'IcatCLIOptions')
from kittens.query_terminal.main import options_spec
do(options_spec(), 'QueryTerminalCLIOptions')
from kittens.panel.main import OPTIONS from kittens.panel.main import OPTIONS
do(OPTIONS(), 'PanelCLIOptions') do(OPTIONS(), 'PanelCLIOptions')

View File

@@ -182,14 +182,9 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
if (!center) {PyErr_SetString(PyExc_RuntimeError, "Failed to get the user notification center"); return NULL; } if (!center) {PyErr_SetString(PyExc_RuntimeError, "Failed to get the user notification center"); return NULL; }
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init]; if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
NSUserNotification *n = [NSUserNotification new]; NSUserNotification *n = [NSUserNotification new];
#define SET(x) { \ if (title) n.title = @(title);
if (x) { \ if (subtitle) n.subtitle = @(subtitle);
NSString *t = @(x); \ if (informativeText) n.informativeText = @(informativeText);
n.x = t; \
[t release]; \
}}
SET(title); SET(subtitle); SET(informativeText);
#undef SET
if (identifier) { if (identifier) {
n.userInfo = @{@"user_id": @(identifier)}; n.userInfo = @{@"user_id": @(identifier)};
} }
@@ -307,17 +302,17 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
@implementation ServiceProvider @implementation ServiceProvider
- (void)openTab:(NSPasteboard*)pasteboard - (BOOL)openTab:(NSPasteboard*)pasteboard
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error { userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
[self openFilesFromPasteboard:pasteboard type:NEW_TAB_WITH_WD]; return [self openFilesFromPasteboard:pasteboard type:NEW_TAB_WITH_WD];
} }
- (void)openOSWindow:(NSPasteboard*)pasteboard - (BOOL)openOSWindow:(NSPasteboard*)pasteboard
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error { userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
[self openFilesFromPasteboard:pasteboard type:NEW_OS_WINDOW_WITH_WD]; return [self openFilesFromPasteboard:pasteboard type:NEW_OS_WINDOW_WITH_WD];
} }
- (void)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type { - (BOOL)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type {
NSDictionary *options = @{ NSPasteboardURLReadingFileURLsOnlyKey: @YES }; NSDictionary *options = @{ NSPasteboardURLReadingFileURLsOnlyKey: @YES };
NSArray *filePathArray = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:options]; NSArray *filePathArray = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:options];
for (NSURL *url in filePathArray) { for (NSURL *url in filePathArray) {
@@ -330,6 +325,7 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
set_cocoa_pending_action(type, [path UTF8String]); set_cocoa_pending_action(type, [path UTF8String]);
} }
} }
return YES;
} }
@end @end

View File

@@ -394,10 +394,10 @@ def complete_files_and_dirs(
def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None: def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
from mimetypes import guess_type from .guess_mime_type import guess_type
def icat_file_predicate(filename: str) -> bool: def icat_file_predicate(filename: str) -> bool:
mt = guess_type(filename)[0] mt = guess_type(filename)
if mt and mt.startswith('image/'): if mt and mt.startswith('image/'):
return True return True
return False return False

View File

@@ -576,6 +576,16 @@ The shape of the mouse pointer when the program running in the terminal grabs th
Valid values are: :code:`arrow`, :code:`beam` and :code:`hand` Valid values are: :code:`arrow`, :code:`beam` and :code:`hand`
''')) '''))
o('default_pointer_shape', 'beam', option_type=choices('arrow', 'beam', 'hand'), long_text=('''
The default shape of the mouse pointer.
Valid values are: :code:`arrow`, :code:`beam` and :code:`hand`
'''))
o('pointer_shape_when_dragging', 'beam', option_type=choices('arrow', 'beam', 'hand'), long_text=('''
The default shape of the mouse pointer when dragging across text.
Valid values are: :code:`arrow`, :code:`beam` and :code:`hand`
'''))
# }}} # }}}
g('performance') # {{{ g('performance') # {{{

View File

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

View File

@@ -161,12 +161,13 @@ coretext_all_fonts(PyObject UNUSED *_self) {
CFArrayRef matches = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection()); CFArrayRef matches = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection());
const CFIndex count = CFArrayGetCount(matches); const CFIndex count = CFArrayGetCount(matches);
PyObject *ans = PyTuple_New(count), *temp; PyObject *ans = PyTuple_New(count), *temp;
if (ans == NULL) return PyErr_NoMemory(); if (ans == NULL) { CFRelease(matches); return PyErr_NoMemory(); }
for (CFIndex i = 0; i < count; i++) { for (CFIndex i = 0; i < count; i++) {
temp = font_descriptor_to_python((CTFontDescriptorRef) CFArrayGetValueAtIndex(matches, i)); temp = font_descriptor_to_python((CTFontDescriptorRef) CFArrayGetValueAtIndex(matches, i));
if (temp == NULL) { Py_DECREF(ans); return NULL; } if (temp == NULL) { CFRelease(matches); Py_DECREF(ans); return NULL; }
PyTuple_SET_ITEM(ans, i, temp); temp = NULL; PyTuple_SET_ITEM(ans, i, temp); temp = NULL;
} }
CFRelease(matches);
return ans; return ans;
} }
@@ -212,6 +213,7 @@ manually_search_fallback_fonts(CTFontRef current_font, CPUCell *cell) {
CFRelease(new_font); CFRelease(new_font);
} }
} }
CFRelease(fonts);
return ans; return ans;
} }

View File

@@ -91,7 +91,7 @@ stop_profiler(PyObject UNUSED *self, PyObject *args UNUSED) {
#endif #endif
static inline bool static inline bool
put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_timeout) { put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_timeout, int optional_actions) {
struct termios raw_termios = *termios_p; struct termios raw_termios = *termios_p;
cfmakeraw(&raw_termios); cfmakeraw(&raw_termios);
if (read_with_timeout) { if (read_with_timeout) {
@@ -99,14 +99,14 @@ put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_time
} else { } else {
raw_termios.c_cc[VMIN] = 1; raw_termios.c_cc[VTIME] = 0; raw_termios.c_cc[VMIN] = 1; raw_termios.c_cc[VTIME] = 0;
} }
if (tcsetattr(fd, TCSAFLUSH, &raw_termios) != 0) { PyErr_SetFromErrno(PyExc_OSError); return false; } if (tcsetattr(fd, optional_actions, &raw_termios) != 0) { PyErr_SetFromErrno(PyExc_OSError); return false; }
return true; return true;
} }
static PyObject* static PyObject*
open_tty(PyObject *self UNUSED, PyObject *args) { open_tty(PyObject *self UNUSED, PyObject *args) {
int read_with_timeout = 0; int read_with_timeout = 0, optional_actions = TCSAFLUSH;
if (!PyArg_ParseTuple(args, "|p", &read_with_timeout)) return NULL; if (!PyArg_ParseTuple(args, "|pi", &read_with_timeout, &optional_actions)) return NULL;
int flags = O_RDWR | O_CLOEXEC | O_NOCTTY; int flags = O_RDWR | O_CLOEXEC | O_NOCTTY;
if (!read_with_timeout) flags |= O_NONBLOCK; if (!read_with_timeout) flags |= O_NONBLOCK;
static char ctty[L_ctermid+1]; static char ctty[L_ctermid+1];
@@ -115,26 +115,26 @@ open_tty(PyObject *self UNUSED, PyObject *args) {
struct termios *termios_p = calloc(1, sizeof(struct termios)); struct termios *termios_p = calloc(1, sizeof(struct termios));
if (!termios_p) return PyErr_NoMemory(); if (!termios_p) return PyErr_NoMemory();
if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; } if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
if (!put_tty_in_raw_mode(fd, termios_p, read_with_timeout != 0)) { free(termios_p); return NULL; } if (!put_tty_in_raw_mode(fd, termios_p, read_with_timeout != 0, optional_actions)) { free(termios_p); return NULL; }
return Py_BuildValue("iN", fd, PyLong_FromVoidPtr(termios_p)); return Py_BuildValue("iN", fd, PyLong_FromVoidPtr(termios_p));
} }
#define TTY_ARGS \ #define TTY_ARGS \
PyObject *tp; int fd; \ PyObject *tp; int fd; int optional_actions = TCSAFLUSH; \
if (!PyArg_ParseTuple(args, "iO!", &fd, &PyLong_Type, &tp)) return NULL; \ if (!PyArg_ParseTuple(args, "iO!|i", &fd, &PyLong_Type, &tp, &optional_actions)) return NULL; \
struct termios *termios_p = PyLong_AsVoidPtr(tp); struct termios *termios_p = PyLong_AsVoidPtr(tp);
static PyObject* static PyObject*
normal_tty(PyObject *self UNUSED, PyObject *args) { normal_tty(PyObject *self UNUSED, PyObject *args) {
TTY_ARGS TTY_ARGS
if (tcsetattr(fd, TCSAFLUSH, termios_p) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } if (tcsetattr(fd, optional_actions, termios_p) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyObject* static PyObject*
raw_tty(PyObject *self UNUSED, PyObject *args) { raw_tty(PyObject *self UNUSED, PyObject *args) {
TTY_ARGS TTY_ARGS
if (!put_tty_in_raw_mode(fd, termios_p, false)) return NULL; if (!put_tty_in_raw_mode(fd, termios_p, false, optional_actions)) return NULL;
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@@ -142,7 +142,7 @@ raw_tty(PyObject *self UNUSED, PyObject *args) {
static PyObject* static PyObject*
close_tty(PyObject *self UNUSED, PyObject *args) { close_tty(PyObject *self UNUSED, PyObject *args) {
TTY_ARGS TTY_ARGS
tcsetattr(fd, TCSAFLUSH, termios_p); // deliberately ignore failure tcsetattr(fd, optional_actions, termios_p); // deliberately ignore failure
free(termios_p); free(termios_p);
safe_close(fd, __FILE__, __LINE__); safe_close(fd, __FILE__, __LINE__);
Py_RETURN_NONE; Py_RETURN_NONE;

View File

@@ -4,6 +4,7 @@ from typing import (
Union Union
) )
import termios
from kitty.boss import Boss from kitty.boss import Boss
from kitty.fonts.render import FontObject from kitty.fonts.render import FontObject
from kitty.options_stub import Options from kitty.options_stub import Options
@@ -865,19 +866,19 @@ def viewport_for_window(
TermiosPtr = NewType('TermiosPtr', int) TermiosPtr = NewType('TermiosPtr', int)
def raw_tty(fd: int, termios_ptr: TermiosPtr) -> None: def raw_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None:
pass pass
def close_tty(fd: int, termios_ptr: TermiosPtr) -> None: def close_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None:
pass pass
def normal_tty(fd: int, termios_ptr: TermiosPtr) -> None: def normal_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None:
pass pass
def open_tty(read_with_timeout: bool = False) -> Tuple[int, TermiosPtr]: def open_tty(read_with_timeout: bool = False, optional_actions: int = termios.TCSAFLUSH) -> Tuple[int, TermiosPtr]:
pass pass

View File

@@ -596,6 +596,7 @@ START_ALLOW_CASE_RANGE
case 0x2500 ... 0x2573: case 0x2500 ... 0x2573:
case 0x2574 ... 0x259f: case 0x2574 ... 0x259f:
case 0xe0b0 ... 0xe0b4: case 0xe0b0 ... 0xe0b4:
case 0x2800 ... 0x28ff:
case 0xe0b6: case 0xe0b6:
case 0xe0b8: //  case 0xe0b8: // 
case 0xe0ba: //  case 0xe0ba: // 
@@ -642,8 +643,10 @@ START_ALLOW_CASE_RANGE
return 0xa0 + ch - 0xe0b0; // IDs from 0xa0 to 0xc4 return 0xa0 + ch - 0xe0b0; // IDs from 0xa0 to 0xc4
case 0x1fb00 ... 0x1fb8b: case 0x1fb00 ... 0x1fb8b:
return 0xc5 + ch - 0x1fb00; // IDs from 0xc5 to 0x150 return 0xc5 + ch - 0x1fb00; // IDs from 0xc5 to 0x150
case 0x1fba0 ... 0x1fbae: case 0x1fba0 ... 0x1fbae: // IDs from 0x151 to 0x15f
return 0x151 + ch - 0x1fba0; return 0x151 + ch - 0x1fba0;
case 0x2800 ... 0x28ff:
return 0x160 + ch - 0x2800;
default: default:
return 0xffff; return 0xffff;
} }

View File

@@ -666,12 +666,12 @@ def smooth_mosaic(
def eight_bar(buf: BufType, width: int, height: int, level: int = 1, which: int = 0, horizontal: bool = False) -> None: def eight_bar(buf: BufType, width: int, height: int, level: int = 1, which: int = 0, horizontal: bool = False) -> None:
if horizontal: if horizontal:
x_range = range(0, width) x_range = range(0, width)
thickness = height // 8 thickness = max(1, height // 8)
y_start = min(which * thickness, height - 2) y_start = min(which * thickness, height - 2)
y_range = range(y_start, height if which == 7 else min(y_start + thickness, height)) y_range = range(y_start, height if which == 7 else min(y_start + thickness, height))
else: else:
y_range = range(0, height) y_range = range(0, height)
thickness = width // 8 thickness = max(1, width // 8)
x_start = min(which * thickness, width - 2) x_start = min(which * thickness, width - 2)
x_range = range(x_start, width if which == 7 else min(x_start + thickness, width)) x_range = range(x_start, width if which == 7 else min(x_start + thickness, width))
for y in y_range: for y in y_range:
@@ -685,6 +685,32 @@ def eight_block(buf: BufType, width: int, height: int, level: int = 1, which: Tu
eight_bar(buf, width, height, level, x, horizontal) eight_bar(buf, width, height, level, x, horizontal)
def braille_dot(buf: BufType, width: int, height: int, col: int, row: int) -> None:
dot_height = max(1, height // 8)
dot_width = max(1, width // 4)
top_margin = (height - 7 * dot_height) // 2
left_margin = (width - 3 * dot_width) // 2
x_start = left_margin + (col * 2 * dot_width)
y_start = top_margin + (row * 2 * dot_height)
if y_start < height:
for y in range(y_start, min(height, y_start + dot_height)):
if x_start < width:
offset = y * width
for x in range(x_start, min(width, x_start + dot_width)):
buf[offset + x] = 255
def braille(buf: BufType, width: int, height: int, which: int = 0) -> None:
if not which:
return
for i, x in enumerate(reversed(bin(which)[2:])):
if x == '1':
q = i + 1
col = 0 if q in (1, 2, 3, 7) else 1
row = 0 if q in (1, 4) else 1 if q in (2, 5) else 2 if q in (3, 6) else 3
braille_dot(buf, width, height, col, row)
box_chars: Dict[str, List[Callable]] = { box_chars: Dict[str, List[Callable]] = {
'': [hline], '': [hline],
'': [p(hline, level=3)], '': [p(hline, level=3)],
@@ -908,6 +934,9 @@ for chars, func_ in (('╒╕╘╛', dvcorner), ('╓╖╙╜', dhcorner), ('
for ch in chars: for ch in chars:
box_chars[ch] = [p(cast(Callable, func_), which=ch)] box_chars[ch] = [p(cast(Callable, func_), which=ch)]
for i in range(256):
box_chars[chr(0x2800 + i)] = [p(braille, which=i)]
c = 0x1fb00 c = 0x1fb00
for i in range(1, 63): for i in range(1, 63):

View File

@@ -421,6 +421,7 @@ make_os_window_context_current(OSWindow *w) {
static inline void static inline void
get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi) { get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi) {
*xscale = 1; *yscale = 1;
if (w) glfwGetWindowContentScale(w, xscale, yscale); if (w) glfwGetWindowContentScale(w, xscale, yscale);
else { else {
GLFWmonitor *monitor = glfwGetPrimaryMonitor(); GLFWmonitor *monitor = glfwGetPrimaryMonitor();
@@ -440,7 +441,7 @@ get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xd
static inline void static inline void
get_window_dpi(GLFWwindow *w, double *x, double *y) { get_window_dpi(GLFWwindow *w, double *x, double *y) {
float xscale = 1, yscale = 1; float xscale, yscale;
get_window_content_scale(w, &xscale, &yscale, x, y); get_window_content_scale(w, &xscale, &yscale, x, y);
} }

57
kitty/guess_mime_type.py Normal file
View File

@@ -0,0 +1,57 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import os
from contextlib import suppress
from typing import Optional
known_extensions = {
'asciidoc': 'text/asciidoctor',
'conf': 'text/config',
'md': 'text/markdown',
'pyj': 'text/rapydscript-ng',
'recipe': 'text/python',
'rst': 'text/restructured-text',
'toml': 'text/toml',
'vim': 'text/vim',
'yaml': 'text/yaml',
'js': 'text/javascript',
'json': 'text/json',
}
text_mimes = ('application/javascript', 'application/x-sh', 'application/json')
def is_rc_file(path: str) -> bool:
name = os.path.basename(path)
return '.' not in name and name.endswith('rc')
def initialize_mime_database() -> None:
if hasattr(initialize_mime_database, 'inited'):
return
setattr(initialize_mime_database, 'inited', True)
from mimetypes import init
init(None)
from kitty.constants import config_dir
local_defs = os.path.join(config_dir, 'mime.types')
if os.path.exists(local_defs):
init((local_defs,))
def guess_type(path: str) -> Optional[str]:
from mimetypes import guess_type as stdlib_guess_type
initialize_mime_database()
mt = None
with suppress(Exception):
mt = stdlib_guess_type(path)[0]
if not mt:
ext = path.rpartition('.')[-1].lower()
mt = known_extensions.get(ext)
if mt in text_mimes:
mt = 'text/' + mt.split('/', 1)[-1]
if not mt and is_rc_file(path):
mt = 'text/plain'
return mt

View File

@@ -63,7 +63,7 @@ alloc_pagerhist(size_t pagerhist_sz) {
if (!ph) return NULL; if (!ph) return NULL;
ph->max_sz = pagerhist_sz; ph->max_sz = pagerhist_sz;
ph->buffer_size = MIN(1024u*1024u, ph->max_sz); ph->buffer_size = MIN(1024u*1024u, ph->max_sz);
ph->buffer = PyMem_RawMalloc(ph->buffer_size); ph->buffer = PyMem_Malloc(ph->buffer_size);
if (!ph->buffer) { PyMem_Free(ph); return NULL; } if (!ph->buffer) { PyMem_Free(ph); return NULL; }
return ph; return ph;
} }
@@ -78,8 +78,8 @@ free_pagerhist(HistoryBuf *self) {
static inline bool static inline bool
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) { pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
if (ph->buffer_size >= ph->max_sz) return false; if (ph->buffer_size >= ph->max_sz) return false;
size_t newsz = ph->buffer_size + MAX(1024u * 1024u, minsz); size_t newsz = MIN(ph->max_sz, ph->buffer_size + MAX(1024u * 1024u, minsz));
uint8_t *newbuf = PyMem_Malloc(MIN(ph->buffer_size + minsz, ph->max_sz)); uint8_t *newbuf = PyMem_Malloc(newsz);
if (!newbuf) return false; if (!newbuf) return false;
size_t copied = MIN(ph->length, ph->buffer_size - ph->start); size_t copied = MIN(ph->length, ph->buffer_size - ph->start);
if (copied) memcpy(newbuf, ph->buffer + ph->start, copied); if (copied) memcpy(newbuf, ph->buffer + ph->start, copied);

2
kitty/key_encoding.py generated
View File

@@ -424,7 +424,7 @@ def update_encoding() -> None:
ans = ENCODING ans = ENCODING
key_map = {} key_map = {}
i = len(ans) i = len(ans)
for k in sorted(keys, key=lambda k: getattr(defines, k)): for k in sorted(keys, key=lambda k: int(getattr(defines, k))):
if k in ('GLFW_KEY_LAST', 'GLFW_KEY_LAST_PRINTABLE'): if k in ('GLFW_KEY_LAST', 'GLFW_KEY_LAST_PRINTABLE'):
continue continue
val = getattr(defines, k) val = getattr(defines, k)

View File

@@ -245,10 +245,8 @@ empty:
error: error:
Py_XDECREF(py_ret); Py_XDECREF(py_ret);
if (procargs != NULL) free(procargs);
free(procargs); free(procenv);
if (procenv != NULL)
free(procargs);
return NULL; return NULL;
} }

View File

@@ -195,6 +195,14 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_
#define HANDLER(name) static inline void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx) #define HANDLER(name) static inline void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx)
static inline void
set_mouse_cursor_when_dragging(void) {
if (mouse_cursor_shape != OPT(pointer_shape_when_dragging)) {
mouse_cursor_shape = OPT(pointer_shape_when_dragging);
set_mouse_cursor(mouse_cursor_shape);
}
}
static inline void static inline void
update_drag(bool from_button, Window *w, bool is_release, int modifiers) { update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
@@ -213,6 +221,7 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
} else if (screen->selections.in_progress) { } else if (screen->selections.in_progress) {
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, false); screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, false);
} }
set_mouse_cursor_when_dragging();
} }
static inline bool static inline bool
@@ -250,6 +259,7 @@ extend_selection(Window *w, bool ended) {
if (screen_has_selection(screen)) { if (screen_has_selection(screen)) {
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, ended, false); screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, ended, false);
} }
set_mouse_cursor_when_dragging();
} }
static inline void static inline void
@@ -265,7 +275,7 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s
// we deliberately allow non-continued lines as some programs, like // we deliberately allow non-continued lines as some programs, like
// mutt split URLs with newlines at line boundaries // mutt split URLs with newlines at line boundaries
index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars); index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars);
if (!new_x) break; if (!new_x && !line_startswith_url_chars(line)) break;
*y += 1; *x = new_x; *y += 1; *x = new_x;
} }
} }
@@ -295,7 +305,7 @@ get_url_sentinel(Line *line, index_type url_start) {
static inline void static inline void
set_mouse_cursor_for_screen(Screen *screen) { set_mouse_cursor_for_screen(Screen *screen) {
mouse_cursor_shape = screen->modes.mouse_tracking_mode == NO_TRACKING ? BEAM : OPT(pointer_shape_when_grabbed); mouse_cursor_shape = screen->modes.mouse_tracking_mode == NO_TRACKING ? OPT(default_pointer_shape): OPT(pointer_shape_when_grabbed);
} }
static inline void static inline void
@@ -397,6 +407,7 @@ multi_click(Window *w, unsigned int count) {
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, mode); screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, mode);
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, true); screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, true);
} }
set_mouse_cursor_when_dragging();
} }
static inline double static inline double

View File

@@ -17,6 +17,7 @@ from .config import KeyAction, parse_key_action
from .constants import config_dir from .constants import config_dir
from .typing import MatchType from .typing import MatchType
from .utils import expandvars, log_error from .utils import expandvars, log_error
from .guess_mime_type import guess_type
class MatchCriteria(NamedTuple): class MatchCriteria(NamedTuple):
@@ -75,12 +76,8 @@ def url_matches_criterion(purl: 'ParseResult', url: str, unquoted_path: str, mc:
if mc.type == 'mime': if mc.type == 'mime':
import fnmatch import fnmatch
from mimetypes import guess_type mt = guess_type(unquoted_path)
try: if not mt:
mt = guess_type(unquoted_path)[0]
except Exception:
return False
if mt is None:
return False return False
mt = mt.lower() mt = mt.lower()
for mpat in mc.value.split(','): for mpat in mc.value.split(','):
@@ -161,8 +158,14 @@ def actions_for_url_from_list(url: str, actions: Iterable[OpenAction]) -> Genera
} }
def expand(x: Any) -> Any: def expand(x: Any) -> Any:
as_bytes = isinstance(x, bytes)
if as_bytes:
x = x.decode('utf-8')
if isinstance(x, str): if isinstance(x, str):
return expandvars(x, env, fallback_to_os_env=False) ans = expandvars(x, env, fallback_to_os_env=False)
if as_bytes:
return ans.encode('utf-8')
return ans
return x return x
for action in actions: for action in actions:

View File

@@ -373,8 +373,10 @@ selection_has_screen_line(const Selections *selections, const int y) {
for (size_t i = 0; i < selections->count; i++) { for (size_t i = 0; i < selections->count; i++) {
const Selection *s = selections->items + i; const Selection *s = selections->items + i;
if (!is_selection_empty(s)) { if (!is_selection_empty(s)) {
int top = (int)s->start.y - s->start_scrolled_by; int start = (int)s->start.y - s->start_scrolled_by;
int bottom = (int)s->end.y - s->end_scrolled_by; int end = (int)s->end.y - s->end_scrolled_by;
int top = MIN(start, end);
int bottom = MAX(start, end);
if (top <= y && y <= bottom) return true; if (top <= y && y <= bottom) return true;
} }
} }
@@ -1311,7 +1313,7 @@ screen_delete_lines(Screen *self, unsigned int count) {
void void
screen_insert_characters(Screen *self, unsigned int count) { screen_insert_characters(Screen *self, unsigned int count) {
unsigned int top = self->margin_top, bottom = self->margin_bottom; const unsigned int top = 0, bottom = self->lines ? self->lines - 1 : 0;
if (count == 0) count = 1; if (count == 0) count = 1;
if (top <= self->cursor->y && self->cursor->y <= bottom) { if (top <= self->cursor->y && self->cursor->y <= bottom) {
unsigned int x = self->cursor->x; unsigned int x = self->cursor->x;
@@ -1327,7 +1329,7 @@ screen_insert_characters(Screen *self, unsigned int count) {
void void
screen_repeat_character(Screen *self, unsigned int count) { screen_repeat_character(Screen *self, unsigned int count) {
unsigned int top = self->margin_top, bottom = self->margin_bottom; const unsigned int top = 0, bottom = self->lines ? self->lines - 1 : 0;
unsigned int x = self->cursor->x; unsigned int x = self->cursor->x;
if (count == 0) count = 1; if (count == 0) count = 1;
if (x > self->columns) return; if (x > self->columns) return;
@@ -1349,7 +1351,7 @@ screen_repeat_character(Screen *self, unsigned int count) {
void void
screen_delete_characters(Screen *self, unsigned int count) { screen_delete_characters(Screen *self, unsigned int count) {
// Delete characters, later characters are moved left // Delete characters, later characters are moved left
unsigned int top = self->margin_top, bottom = self->margin_bottom; const unsigned int top = 0, bottom = self->lines ? self->lines - 1 : 0;
if (count == 0) count = 1; if (count == 0) count = 1;
if (top <= self->cursor->y && self->cursor->y <= bottom) { if (top <= self->cursor->y && self->cursor->y <= bottom) {
unsigned int x = self->cursor->x; unsigned int x = self->cursor->x;

View File

@@ -115,7 +115,7 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None)
w: Optional[Watchers] = None w: Optional[Watchers] = None
if t.watchers.has_watchers: if t.watchers.has_watchers:
w = t.watchers.copy() w = t.watchers.copy()
t.windows.append(SpecialWindow(cmd=resolved_shell(opts), watchers=w)) t.windows.append(SpecialWindow(cmd=resolved_shell(opts), watchers=w, override_title=default_title))
return ans return ans
ans = Session(default_title) ans = Session(default_title)

View File

@@ -703,6 +703,8 @@ PYWRAP1(set_options) {
S(resize_in_steps, PyObject_IsTrue); S(resize_in_steps, PyObject_IsTrue);
S(allow_hyperlinks, PyObject_IsTrue); S(allow_hyperlinks, PyObject_IsTrue);
S(pointer_shape_when_grabbed, pointer_shape); S(pointer_shape_when_grabbed, pointer_shape);
S(default_pointer_shape, pointer_shape);
S(pointer_shape_when_dragging, pointer_shape);
GA(tab_bar_style); GA(tab_bar_style);
global_state.tab_bar_hidden = PyUnicode_CompareWithASCIIString(ret, "hidden") == 0 ? true: false; global_state.tab_bar_hidden = PyUnicode_CompareWithASCIIString(ret, "hidden") == 0 ? true: false;

View File

@@ -66,6 +66,8 @@ typedef struct {
bool allow_hyperlinks; bool allow_hyperlinks;
monotonic_t resize_debounce_time; monotonic_t resize_debounce_time;
MouseShape pointer_shape_when_grabbed; MouseShape pointer_shape_when_grabbed;
MouseShape default_pointer_shape;
MouseShape pointer_shape_when_dragging;
struct { struct {
UrlPrefix *values; UrlPrefix *values;
size_t num, max_prefix_len; size_t num, max_prefix_len;

View File

@@ -4,7 +4,10 @@
import re import re
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from typing import cast, Dict from typing import TYPE_CHECKING, Dict, Generator, Optional, cast
if TYPE_CHECKING:
from .options_stub import Options
def modify_key_bytes(keybytes: bytes, amt: int) -> bytes: def modify_key_bytes(keybytes: bytes, amt: int) -> bytes:
@@ -449,28 +452,42 @@ def key_as_bytes(name: str) -> bytes:
return ans.encode('ascii') return ans.encode('ascii')
def get_capabilities(query_string: str) -> str: def get_capabilities(query_string: str, opts: 'Options') -> Generator[str, None, None]:
from .fast_data_types import ERROR_PREFIX from .fast_data_types import ERROR_PREFIX
ans = []
try: def result(encoded_query_name: str, x: Optional[str] = None) -> str:
for q in query_string.split(';'): if x is None:
name = qname = unhexlify(q).decode('utf-8') return '0+r' + encoded_query_name
if name in ('TN', 'name'): return '1+r' + encoded_query_name + '=' + hexlify(str(x).encode('utf-8')).decode('ascii')
val = names[0]
for encoded_query_name in query_string.split(';'):
name = qname = unhexlify(encoded_query_name).decode('utf-8')
if name in ('TN', 'name'):
yield result(encoded_query_name, names[0])
elif name.startswith('kitty-query-'):
name = name[len('kitty-query-'):]
if name == 'version':
from .constants import str_version
yield result(encoded_query_name, str_version)
elif name == 'allow_hyperlinks':
yield result(encoded_query_name,
'ask' if opts.allow_hyperlinks == 0b11 else ('yes' if opts.allow_hyperlinks else 'no'))
else: else:
from .utils import log_error
log_error('Unknown kitty terminfo query:', name)
yield result(encoded_query_name)
else:
try:
val = queryable_capabilities[name]
except KeyError:
try: try:
val = queryable_capabilities[name] qname = termcap_aliases[name]
except KeyError: val = queryable_capabilities[qname]
try: except Exception:
qname = termcap_aliases[name] from .utils import log_error
val = queryable_capabilities[qname] log_error(ERROR_PREFIX, 'Unknown terminfo property:', name)
except Exception: yield result(encoded_query_name)
from .utils import log_error continue
log_error(ERROR_PREFIX, 'Unknown terminfo property:', name) if qname in string_capabilities and '%' not in val:
raise val = key_as_bytes(qname).decode('ascii')
if qname in string_capabilities and '%' not in val: yield result(encoded_query_name, val)
val = key_as_bytes(qname).decode('ascii')
ans.append(q + '=' + hexlify(str(val).encode('utf-8')).decode('ascii'))
return '1+r' + ';'.join(ans)
except Exception:
return '0+r' + query_string

View File

@@ -90,7 +90,9 @@ def save_notification(version: Version) -> None:
for version in sorted(notified_versions): for version in sorted(notified_versions):
n = notified_versions[version] n = notified_versions[version]
lines.append('{},{},{}'.format( lines.append('{},{},{}'.format(
'.'.join(map(str, n.version)), n.time_of_last_notification, n.count)) '.'.join(map(str, n.version)),
n.time_of_last_notification,
n.notification_count))
atomic_save('\n'.join(lines).encode('utf-8'), version_notification_log()) atomic_save('\n'.join(lines).encode('utf-8'), version_notification_log())

View File

@@ -580,6 +580,13 @@ def parse_uri_list(text: str) -> Generator[str, None, None]:
yield unquote(purl.path) yield unquote(purl.path)
def edit_config_file() -> None:
from kitty.config import prepare_config_file_for_editing
p = prepare_config_file_for_editing()
editor = get_editor()
os.execvp(editor[0], editor + [p])
class SSHConnectionData(NamedTuple): class SSHConnectionData(NamedTuple):
binary: str binary: str
hostname: str hostname: str

View File

@@ -704,7 +704,8 @@ class Window:
self.refresh() self.refresh()
def request_capabilities(self, q: str) -> None: def request_capabilities(self, q: str) -> None:
self.screen.send_escape_code_to_child(DCS, get_capabilities(q)) for result in get_capabilities(q, self.opts):
self.screen.send_escape_code_to_child(DCS, result)
def handle_remote_cmd(self, cmd: str) -> None: def handle_remote_cmd(self, cmd: str) -> None:
get_boss().handle_remote_cmd(cmd, self) get_boss().handle_remote_cmd(cmd, self)

View File

@@ -31,8 +31,8 @@ class Callbacks:
def request_capabilities(self, q): def request_capabilities(self, q):
from kitty.terminfo import get_capabilities from kitty.terminfo import get_capabilities
c = get_capabilities(q) for c in get_capabilities(q, None):
self.write(c.encode('ascii')) self.write(c.encode('ascii'))
def use_utf8(self, on): def use_utf8(self, on):
self.iutf8 = on self.iutf8 = on

View File

@@ -30,3 +30,31 @@ class TestHints(BaseTest):
t('link:{}[xxx]'.format(u), u) t('link:{}[xxx]'.format(u), u)
t('`xyz <{}>`_.'.format(u), u) t('`xyz <{}>`_.'.format(u), u)
t('<a href="{}">moo'.format(u), u) t('<a href="{}">moo'.format(u), u)
def test_ip_hints(self):
from kittens.hints.main import parse_hints_args, functions_for, mark, convert_text
args = parse_hints_args(['--type', 'ip'])[0]
pattern, post_processors = functions_for(args)
def create_marks(text, cols=60):
text = convert_text(text, cols)
return tuple(mark(pattern, post_processors, text, args))
testcases = (
('100.64.0.0', ['100.64.0.0']),
('2001:0db8:0000:0000:0000:ff00:0042:8329', ['2001:0db8:0000:0000:0000:ff00:0042:8329']),
('2001:db8:0:0:0:ff00:42:8329', ['2001:db8:0:0:0:ff00:42:8329']),
('2001:db8::ff00:42:8329', ['2001:db8::ff00:42:8329']),
('2001:DB8::FF00:42:8329', ['2001:DB8::FF00:42:8329']),
('0000:0000:0000:0000:0000:0000:0000:0001', ['0000:0000:0000:0000:0000:0000:0000:0001']),
('::1', ['::1']),
# Invalid IPs won't match
('255.255.255.256', []),
(':1', []),
)
for testcase, expected in testcases:
with self.subTest(testcase=testcase, expected=expected):
marks = create_marks(testcase)
ips = [m.text for m in marks]
self.ae(ips, expected)

View File

@@ -142,6 +142,13 @@ class TestScreen(BaseTest):
self.ae(str(s.line(0)), 'ade') self.ae(str(s.line(0)), 'ade')
self.assertTrue(s.line(0).cursor_from(4).bold) self.assertTrue(s.line(0).cursor_from(4).bold)
self.assertFalse(s.line(0).cursor_from(2).bold) self.assertFalse(s.line(0).cursor_from(2).bold)
s = self.create_screen()
s.set_margins(1, 2)
s.cursor.y = 3
s.draw('abcde')
s.cursor.x = 0
s.delete_characters(2)
self.ae('cde', str(s.line(s.cursor.y)))
init() init()
s.erase_characters(2) s.erase_characters(2)

View File

@@ -42,10 +42,11 @@ safe_realpath(const char* src, char *buf, size_t buf_sz) {
#endif #endif
static inline void static inline void
set_xoptions(const wchar_t *exe_dir, const char *lc_ctype) { set_xoptions(const wchar_t *exe_dir, const char *lc_ctype, bool from_source) {
wchar_t buf[PATH_MAX+1] = {0}; wchar_t buf[PATH_MAX+1] = {0};
swprintf(buf, PATH_MAX, L"bundle_exe_dir=%ls", exe_dir); swprintf(buf, PATH_MAX, L"bundle_exe_dir=%ls", exe_dir);
PySys_AddXOption(buf); PySys_AddXOption(buf);
if (from_source) PySys_AddXOption(L"kitty_from_source=1");
if (lc_ctype) { if (lc_ctype) {
swprintf(buf, PATH_MAX, L"lc_ctype_before_python=%s", lc_ctype); swprintf(buf, PATH_MAX, L"lc_ctype_before_python=%s", lc_ctype);
PySys_AddXOption(buf); PySys_AddXOption(buf);
@@ -66,7 +67,6 @@ static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wch
int ret = 1; int ret = 1;
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL); wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir\n"); return 1; } if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir\n"); return 1; }
set_xoptions(exe_dir, lc_ctype);
wchar_t stdlib[PATH_MAX+1] = {0}; wchar_t stdlib[PATH_MAX+1] = {0};
#ifdef __APPLE__ #ifdef __APPLE__
const char *python_relpath = "../Resources/Python/lib"; const char *python_relpath = "../Resources/Python/lib";
@@ -80,9 +80,9 @@ static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wch
); );
if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to python stdlib\n"); return 1; } if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to python stdlib\n"); return 1; }
Py_SetPath(stdlib); Py_SetPath(stdlib);
PyMem_RawFree(exe_dir);
if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to kitty lib\n"); return 1; }
Py_Initialize(); Py_Initialize();
set_xoptions(exe_dir, lc_ctype, false);
PyMem_RawFree(exe_dir);
PySys_SetArgvEx(argc - 1, argv + 1, 0); PySys_SetArgvEx(argc - 1, argv + 1, 0);
PySys_SetObject("frozen", Py_True); PySys_SetObject("frozen", Py_True);
PyObject *kitty = PyUnicode_FromString(libpath); PyObject *kitty = PyUnicode_FromString(libpath);
@@ -104,10 +104,11 @@ static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wch
(void)libpath; (void)libpath;
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL); wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_); return 1; } if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_); return 1; }
set_xoptions(exe_dir, lc_ctype); bool from_source = false;
#ifdef FROM_SOURCE #ifdef FROM_SOURCE
PySys_AddXOption(L"kitty_from_source=1"); from_source = true;
#endif #endif
set_xoptions(exe_dir, lc_ctype, from_source);
PyMem_RawFree(exe_dir); PyMem_RawFree(exe_dir);
return Py_Main(argc, argv); return Py_Main(argc, argv);
} }

View File

@@ -1 +0,0 @@
See https://sw.kovidgoyal.net/kitty/protocol-extensions.html

View File

@@ -68,14 +68,15 @@ def run_html(args: Any) -> None:
def add_analytics() -> None: def add_analytics() -> None:
analytics = ''' analytics = '''
<!-- Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-20736318-2"></script>
<script> <script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date; window.dataLayer = window.dataLayer || [];
ga('create', 'UA-20736318-2', 'auto'); function gtag(){dataLayer.push(arguments);}
ga('send', 'pageview'); gtag('js', new Date());
gtag('config', 'UA-20736318-2');
</script> </script>
<script async="async" src='https://www.google-analytics.com/analytics.js'></script>
<!-- End Google Analytics -->\
''' '''
for dirpath, firnames, filenames in os.walk(publish_dir): for dirpath, firnames, filenames in os.walk(publish_dir):
for fname in filenames: for fname in filenames:
@@ -87,6 +88,10 @@ ga('send', 'pageview');
f.write(html.encode('utf-8')) f.write(html.encode('utf-8'))
def run_docs(args: Any) -> None:
subprocess.check_call(['make', 'docs'])
def run_website(args: Any) -> None: def run_website(args: Any) -> None:
if os.path.exists(publish_dir): if os.path.exists(publish_dir):
shutil.rmtree(publish_dir) shutil.rmtree(publish_dir)
@@ -132,7 +137,7 @@ class ReadFileWithProgressReporting(io.FileIO): # {{{
def __len__(self) -> int: def __len__(self) -> int:
return self._total return self._total
def read(self, size: int = -1) -> Optional[bytes]: def read(self, size: int = -1) -> bytes:
data = io.FileIO.read(self, size) data = io.FileIO.read(self, size)
if data: if data:
self.report_progress(len(data)) self.report_progress(len(data))
@@ -384,6 +389,8 @@ def main() -> None:
ans = 'n' ans = 'n'
if ans.lower() != 'y': if ans.lower() != 'y':
return return
if actions == ['website']:
actions.insert(0, 'docs')
for action in actions: for action in actions:
print('Running', action) print('Running', action)
cwd = os.getcwd() cwd = os.getcwd()

View File

@@ -1 +0,0 @@
See https://sw.kovidgoyal.net/kitty/remote-control.html

View File

@@ -63,11 +63,11 @@ class Options(argparse.Namespace):
for_freeze: bool = False for_freeze: bool = False
libdir_name: str = 'lib' libdir_name: str = 'lib'
extra_logging: List[str] = [] extra_logging: List[str] = []
link_time_optimization: bool = True link_time_optimization: bool = 'KITTY_NO_LTO' not in os.environ
update_check_interval: float = 24 update_check_interval: float = 24
egl_library: Optional[str] = None egl_library: Optional[str] = os.getenv('KITTY_EGL_LIBRARY')
startup_notification_library: Optional[str] = None startup_notification_library: Optional[str] = os.getenv('KITTY_STARTUP_NOTIFICATION_LIBRARY')
canberra_library: Optional[str] = None canberra_library: Optional[str] = os.getenv('KITTY_CANBERRA_LIBRARY')
class CompileKey(NamedTuple): class CompileKey(NamedTuple):
@@ -211,11 +211,11 @@ def get_sanitize_args(cc: str, ccver: Tuple[int, int]) -> List[str]:
return sanitize_args return sanitize_args
def test_compile(cc: str, *cflags: str, src: Optional[str] = None) -> bool: def test_compile(cc: str, *cflags: str, src: Optional[str] = None, lang: str = 'c') -> bool:
src = src or 'int main(void) { return 0; }' src = src or 'int main(void) { return 0; }'
p = subprocess.Popen( p = subprocess.Popen(
[cc] + list(cflags) + ['-x', 'c', '-o', os.devnull, '-'], [cc] + list(cflags) + ['-x', lang, '-o', os.devnull, '-'],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.PIPE stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.PIPE,
) )
stdin = p.stdin stdin = p.stdin
assert stdin is not None assert stdin is not None
@@ -227,9 +227,9 @@ def test_compile(cc: str, *cflags: str, src: Optional[str] = None) -> bool:
return p.wait() == 0 return p.wait() == 0
def first_successful_compile(cc: str, *cflags: str, src: Optional[str] = None) -> str: def first_successful_compile(cc: str, *cflags: str, src: Optional[str] = None, lang: str = 'c') -> str:
for x in cflags: for x in cflags:
if test_compile(cc, *shlex.split(x), src=src): if test_compile(cc, *shlex.split(x), src=src, lang=lang):
return x return x
return '' return ''
@@ -340,7 +340,10 @@ def kitty_env() -> Env:
platform_libs = [ platform_libs = [
'-framework', 'CoreText', '-framework', 'CoreGraphics', '-framework', 'CoreText', '-framework', 'CoreGraphics',
] ]
user_notifications_framework = first_successful_compile(ans.cc, '-framework UserNotifications') test_program_src = '''#include <UserNotifications/UserNotifications.h>
int main(void) { return 0; }\n'''
user_notifications_framework = first_successful_compile(
ans.cc, '-framework UserNotifications', src=test_program_src, lang='objective-c')
if user_notifications_framework: if user_notifications_framework:
platform_libs.extend(shlex.split(user_notifications_framework)) platform_libs.extend(shlex.split(user_notifications_framework))
else: else:
@@ -1176,8 +1179,6 @@ def option_parser() -> argparse.ArgumentParser: # {{{
def main() -> None: def main() -> None:
global verbose global verbose
if sys.version_info < (3, 5):
raise SystemExit('python >= 3.5 required')
args = option_parser().parse_args(namespace=Options()) args = option_parser().parse_args(namespace=Options())
verbose = args.verbose > 0 verbose = args.verbose > 0
args.prefix = os.path.abspath(args.prefix) args.prefix = os.path.abspath(args.prefix)

55
shell.nix Normal file
View File

@@ -0,0 +1,55 @@
{ pkgs ? import <nixpkgs> { } }:
with pkgs;
let
inherit (lib) optional optionals;
inherit (xorg) libX11 libXrandr libXinerama libXcursor libXi libXext;
inherit (darwin.apple_sdk.frameworks) Cocoa CoreGraphics Foundation IOKit Kernel OpenGL;
harfbuzzWithCoreText = harfbuzz.override { withCoreText = stdenv.isDarwin; };
in
mkShell rec {
buildInputs = [
harfbuzzWithCoreText
ncurses
lcms2
] ++ optionals stdenv.isDarwin [
Cocoa
CoreGraphics
Foundation
IOKit
Kernel
OpenGL
libpng
python3
zlib
] ++ optionals stdenv.isLinux [
fontconfig libunistring libcanberra libX11
libXrandr libXinerama libXcursor libxkbcommon libXi libXext
wayland-protocols wayland dbus
] ++ checkInputs;
nativeBuildInputs = [
pkgconfig python3Packages.sphinx ncurses
] ++ optionals stdenv.isDarwin [
imagemagick
libicns # For the png2icns tool.
installShellFiles
];
propagatedBuildInputs = optional stdenv.isLinux libGL;
checkInputs = [
python3Packages.pillow
];
# Causes build failure due to warning when using Clang
hardeningDisable = [ "strictoverflow" ];
shellHook = if stdenv.isDarwin then ''
export KITTY_NO_LTO=
'' else ''
export KITTY_EGL_LIBRARY='${stdenv.lib.getLib libGL}/lib/libEGL.so.1'
export KITTY_STARTUP_NOTIFICATION_LIBRARY='${libstartup_notification}/lib/libstartup-notification-1.so'
export KITTY_CANBERRA_LIBRARY='${libcanberra}/lib/libcanberra.so'
'';
}

View File

@@ -97,7 +97,7 @@ def run_tests() -> None:
def run_cli(suite: unittest.TestSuite, verbosity: int = 4) -> None: def run_cli(suite: unittest.TestSuite, verbosity: int = 4) -> None:
r = unittest.TextTestRunner r = unittest.TextTestRunner
r.resultclass = unittest.TextTestResult # type: ignore r.resultclass = unittest.TextTestResult
init_env() init_env()
runner = r(verbosity=verbosity) runner = r(verbosity=verbosity)
runner.tb_locals = True # type: ignore runner.tb_locals = True # type: ignore