Compare commits
255 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9903b8e8db | ||
|
|
1d18f63eb5 | ||
|
|
a63682b160 | ||
|
|
812f3e60ca | ||
|
|
f8f5838ab5 | ||
|
|
07782e071a | ||
|
|
624dfdf913 | ||
|
|
a01dad8c3c | ||
|
|
b6baa99fce | ||
|
|
912b395316 | ||
|
|
b1b90d2bf7 | ||
|
|
37bd964fee | ||
|
|
fa3e173c18 | ||
|
|
2a9dc23c61 | ||
|
|
2885f54360 | ||
|
|
ac2f4c8ab6 | ||
|
|
7282d8edc1 | ||
|
|
6f2d63eb87 | ||
|
|
9cab8a2de5 | ||
|
|
709d19119d | ||
|
|
029b82807c | ||
|
|
67c0942e01 | ||
|
|
fb57653665 | ||
|
|
646a349d82 | ||
|
|
ad1109b6fe | ||
|
|
c2bd6a66d9 | ||
|
|
ba9af3df8c | ||
|
|
5d4e968f50 | ||
|
|
0e327f3d13 | ||
|
|
bb06bfa627 | ||
|
|
2a713cab60 | ||
|
|
26df57a1c7 | ||
|
|
18893f86ce | ||
|
|
8fead5ebe8 | ||
|
|
6020aadbf2 | ||
|
|
f8b6b532e0 | ||
|
|
05ec174868 | ||
|
|
3875ee021a | ||
|
|
2931afa0ae | ||
|
|
2803e45380 | ||
|
|
965dde599e | ||
|
|
229ea45d81 | ||
|
|
8ea84c97d5 | ||
|
|
7c47bd547f | ||
|
|
6ae58e6882 | ||
|
|
3bc0f90a82 | ||
|
|
2a99ef254f | ||
|
|
5bc811f26e | ||
|
|
83893d762a | ||
|
|
24bed6273b | ||
|
|
2a5b314f67 | ||
|
|
e3c9fc3fba | ||
|
|
b81700a8fd | ||
|
|
b319fd4b93 | ||
|
|
23d6995558 | ||
|
|
190612d507 | ||
|
|
cddfe425e1 | ||
|
|
bc6a03a41b | ||
|
|
e053d1f566 | ||
|
|
e429b8484c | ||
|
|
22159c5eb4 | ||
|
|
903df9972a | ||
|
|
4dcbb1f077 | ||
|
|
5158d49781 | ||
|
|
3fdf47c535 | ||
|
|
b59d1bda8b | ||
|
|
1760fb9db2 | ||
|
|
082c771fc0 | ||
|
|
7ec989d485 | ||
|
|
0f9613eb9e | ||
|
|
85a354351a | ||
|
|
24db8cef4f | ||
|
|
9908eee40c | ||
|
|
b6517758d9 | ||
|
|
029500f3ed | ||
|
|
686727a7c3 | ||
|
|
91a1d61cc6 | ||
|
|
b8e9921f3d | ||
|
|
e0ce5d5629 | ||
|
|
ed511c296c | ||
|
|
eab1fbbbb0 | ||
|
|
ee3b052a50 | ||
|
|
5b98f15d9b | ||
|
|
51d76e5a2b | ||
|
|
a6bce0b221 | ||
|
|
fb5dc8a2ba | ||
|
|
d571ee0d01 | ||
|
|
5d7ed67316 | ||
|
|
9f6c754339 | ||
|
|
2819a4ebf3 | ||
|
|
dec1203ea8 | ||
|
|
87ab0d7b18 | ||
|
|
043530409c | ||
|
|
9710282d22 | ||
|
|
5e8e141fb5 | ||
|
|
2e313fbdd2 | ||
|
|
5c3e4db05f | ||
|
|
2be2f6aa0c | ||
|
|
6d038f5cdf | ||
|
|
27feccba39 | ||
|
|
7d39d5c088 | ||
|
|
2e69b904da | ||
|
|
79dcc8c771 | ||
|
|
683e5a4754 | ||
|
|
d51a424b98 | ||
|
|
e07d216a11 | ||
|
|
b9fa8dbe42 | ||
|
|
3820c46801 | ||
|
|
b663160bc2 | ||
|
|
a65c807a4a | ||
|
|
3a1f85cb69 | ||
|
|
196bd9c22b | ||
|
|
9bf2087b55 | ||
|
|
51e589c7e6 | ||
|
|
7365ed9b05 | ||
|
|
9cb7a6b2c7 | ||
|
|
9ff7eae1af | ||
|
|
61e1b74434 | ||
|
|
324c223d54 | ||
|
|
96793a296c | ||
|
|
7b1626a38f | ||
|
|
09a3a3dafb | ||
|
|
0e21708f25 | ||
|
|
89fa2343a6 | ||
|
|
03256d8ac9 | ||
|
|
889ca77912 | ||
|
|
23ee6b3109 | ||
|
|
d133ffac25 | ||
|
|
ffc6e65392 | ||
|
|
d61086907d | ||
|
|
5b58c2892d | ||
|
|
67379c67cc | ||
|
|
4691257233 | ||
|
|
d46a8c893a | ||
|
|
577ba0b39d | ||
|
|
f761245280 | ||
|
|
f1b4e585aa | ||
|
|
255db2cb66 | ||
|
|
8b6240a067 | ||
|
|
f5c713b118 | ||
|
|
ac7596b7c5 | ||
|
|
f69ed984a4 | ||
|
|
83850b28ae | ||
|
|
5efa26a98d | ||
|
|
51d2c01bc1 | ||
|
|
be9d876997 | ||
|
|
866b53f384 | ||
|
|
de1a2f7682 | ||
|
|
86686bfac7 | ||
|
|
3f316c39d1 | ||
|
|
14459a7d18 | ||
|
|
c062b84bc6 | ||
|
|
890a68fa53 | ||
|
|
59017a6e6f | ||
|
|
06cb2cafe6 | ||
|
|
e4ed170748 | ||
|
|
f011a6c86c | ||
|
|
45fc1ace0c | ||
|
|
8d51edec17 | ||
|
|
f2afba2ef4 | ||
|
|
efcd3a5df7 | ||
|
|
2a52acdef4 | ||
|
|
eadfeeec9b | ||
|
|
f8a581129f | ||
|
|
93ff651d9e | ||
|
|
bec0f02b11 | ||
|
|
0721951c79 | ||
|
|
b37d07d31a | ||
|
|
9cacfc0c26 | ||
|
|
0b93b85cf2 | ||
|
|
f7001ea068 | ||
|
|
aa93c3fb66 | ||
|
|
166c7bf0e6 | ||
|
|
bb1374f64e | ||
|
|
053835afbe | ||
|
|
4760580d1c | ||
|
|
8fa492e0ee | ||
|
|
040ae75c77 | ||
|
|
759aaf294a | ||
|
|
9ea0de868c | ||
|
|
78002cf3ba | ||
|
|
f68f8bf9c9 | ||
|
|
54438cae3f | ||
|
|
2e693c31df | ||
|
|
0f06bf1bdb | ||
|
|
b7f4f2e898 | ||
|
|
7148ee8a0f | ||
|
|
238508fc0a | ||
|
|
84ab0f5062 | ||
|
|
cf04bdb36a | ||
|
|
d09a30d16c | ||
|
|
d54b58d2f3 | ||
|
|
0be0963dc7 | ||
|
|
61a2360df5 | ||
|
|
b3ed4e3bc2 | ||
|
|
316e7cb9f5 | ||
|
|
23851e31bf | ||
|
|
b7b64ec513 | ||
|
|
2b372373ae | ||
|
|
7a543fac86 | ||
|
|
5c4b14468c | ||
|
|
7d4d978d97 | ||
|
|
fe5b8f3aec | ||
|
|
f5abe7cbf7 | ||
|
|
bd0382830a | ||
|
|
0b99bb534f | ||
|
|
9d67198ff9 | ||
|
|
fda57a81e1 | ||
|
|
9962ac2703 | ||
|
|
edc4b91d15 | ||
|
|
92ca8ab9cc | ||
|
|
07e2d9175c | ||
|
|
c90dbb25b9 | ||
|
|
b5da109e7f | ||
|
|
aa18429a8c | ||
|
|
e578d41adb | ||
|
|
f3579e78af | ||
|
|
9c43707f40 | ||
|
|
99ff92a5a7 | ||
|
|
d866c0d202 | ||
|
|
20c42ab519 | ||
|
|
8bbc2b82d4 | ||
|
|
6929358976 | ||
|
|
82acd6b511 | ||
|
|
bb939ed4f8 | ||
|
|
cd25abc49f | ||
|
|
2e90e443ed | ||
|
|
92bb6e293a | ||
|
|
ea9b431386 | ||
|
|
23077fd6a6 | ||
|
|
5eee7ce338 | ||
|
|
784894cac3 | ||
|
|
757983b729 | ||
|
|
b6660353cd | ||
|
|
215a7689d9 | ||
|
|
dd18a1dfd8 | ||
|
|
32f16ee5f5 | ||
|
|
d4f426d2ce | ||
|
|
a6d96c9a14 | ||
|
|
bf3bfeaf69 | ||
|
|
6ce82177ea | ||
|
|
dc6efb5b37 | ||
|
|
b26d1f306d | ||
|
|
cf678d8179 | ||
|
|
8cecb22481 | ||
|
|
2504266806 | ||
|
|
e3795eb07e | ||
|
|
796ead5d75 | ||
|
|
d5907a1bf9 | ||
|
|
ddd4774169 | ||
|
|
1dc83a47cf | ||
|
|
0a8dc4be0a | ||
|
|
3d99361790 | ||
|
|
1e278938cc | ||
|
|
ba177c61bc |
@@ -6,7 +6,7 @@ matrix:
|
||||
group: beta
|
||||
sudo: false
|
||||
env:
|
||||
- CC=gcc SANITIZE_ARG=--sanitize
|
||||
- CC=gcc CFLAGS=-funsigned-char SANITIZE_ARG=--sanitize
|
||||
language: python
|
||||
python: "3.5"
|
||||
addons:
|
||||
@@ -96,7 +96,7 @@ install: |
|
||||
fi
|
||||
else
|
||||
pushd /tmp
|
||||
wget http://xkbcommon.org/download/libxkbcommon-0.6.1.tar.xz
|
||||
wget --no-check-certificate http://xkbcommon.org/download/libxkbcommon-0.6.1.tar.xz
|
||||
tar xf libxkbcommon-0.6.1.tar.xz
|
||||
cd libxkbcommon-0.6.1
|
||||
./configure --prefix=$HOME/harfbuzz --disable-dependency-tracking --disable-static
|
||||
|
||||
122
CHANGELOG.rst
122
CHANGELOG.rst
@@ -3,6 +3,128 @@ Changelog
|
||||
|
||||
kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
|
||||
version 0.10.0 [2018-05-21]
|
||||
------------------------------
|
||||
|
||||
- A diff kitten to show side-by-side diffs with syntax highlighting and support
|
||||
for images. See `diff README
|
||||
<https://github.com/kovidgoyal/kitty/tree/master/kittens/diff>`_.
|
||||
|
||||
- Make windows in the various kitty layouts manually resizable. See the
|
||||
`Layouts section of the README
|
||||
<https://github.com/kovidgoyal/kitty#layouts>`_ for details.
|
||||
|
||||
- Implement support for the SGR *faint* escape code to make text blend
|
||||
into the background (`#446 <https://github.com/kovidgoyal/kitty/issues/446>`_).
|
||||
|
||||
- Make the hints kitten a little `smarter
|
||||
<https://github.com/kovidgoyal/kitty/commit/ad1109b6fe0a6802ca4f77182a7a0b36086b3e9f>`_,
|
||||
so that URLs that stretch over multiple lines are detected. Also improve
|
||||
detection of surrounding brackets/quotes.
|
||||
|
||||
- Make the kitty window id available as the environment variable
|
||||
`KITTY_WINDOW_ID` (`#532 <https://github.com/kovidgoyal/kitty/issues/532>`_).
|
||||
|
||||
- Add a "fat" layout that is similar to the "tall" layout but vertically
|
||||
oriented.
|
||||
|
||||
- Expand environment variables in config file include directives
|
||||
|
||||
- Allow programs running in kitty to `read/write from the clipboard
|
||||
<https://github.com/kovidgoyal/kitty/commit/889ca7791244253cb08fbc3eca8883a87fb943a7>`_.
|
||||
By default only writing is allowed.
|
||||
|
||||
- Fix moving cursor outside a defined page area incorrectly causing the cursor
|
||||
to be placed inside the page area. Caused incorrect rendering in neovim, in
|
||||
some situations.
|
||||
|
||||
- Render a couple more powerline symbols directly, bypassing the font
|
||||
(`#550 <https://github.com/kovidgoyal/kitty/issues/550>`_).
|
||||
|
||||
- Fix ctrl+alt+<special> not working in normal and application keyboard (`#548
|
||||
<https://github.com/kovidgoyal/kitty/issues/548>`_).
|
||||
|
||||
- Partial fix for rendering Right-to-left languages like Arabic. Rendering of
|
||||
Arabic is never going to be perfect, but now it is at least readable.
|
||||
|
||||
- Fix Ctrl+backspace acting as plain backspace in normal and application
|
||||
keyboard modes (`#538 <https://github.com/kovidgoyal/kitty/issues/538>`_).
|
||||
|
||||
- Have the paste_from_selection action paste from the clipboard on platforms
|
||||
that do not have a primary selection such as Wayland and macOS
|
||||
(`#529 <https://github.com/kovidgoyal/kitty/issues/529>`_)
|
||||
|
||||
- Fix cursor_stop_blinking_after=0 not working (`#530 <https://github.com/kovidgoyal/kitty/issues/530>`_)
|
||||
|
||||
|
||||
version 0.9.1 [2018-05-05]
|
||||
------------------------------
|
||||
|
||||
- Show a bell symbol on the tab if a bell occurs in one of the windows in the tab and
|
||||
the window is not the currently focused window
|
||||
|
||||
- Change the window border color if a bell occurs in an unfocused window. Can
|
||||
be disabled by setting the bell_border_color to be the same as the
|
||||
inactive_border_color.
|
||||
|
||||
- macOS: Add support for dead keys
|
||||
|
||||
- Unicode input: When searching by name search for prefix matches as well as
|
||||
whole word matches
|
||||
|
||||
- Dynamically allocate the memory used for the scrollback history buffer.
|
||||
Reduces startup memory consumption when using very large scrollback
|
||||
buffer sizes.
|
||||
|
||||
- Add an option to not request window attention on bell.
|
||||
|
||||
- Remote control: Allow matching windows by number (visible position).
|
||||
|
||||
- macOS: Fix changing tab title and kitty shell not working
|
||||
|
||||
- When triple-clicking select all wrapped lines belonging to a single logical line.
|
||||
|
||||
- hints kitten: Detect bracketed URLs and dont include the closing bracket in the URL.
|
||||
|
||||
- When calling pass_selection_to_program use the current directory of the child
|
||||
process as the cwd of the program.
|
||||
|
||||
- Add macos_hide_from_tasks option to hide kitty from the macOS task switcher
|
||||
|
||||
- macOS: When the macos_titlebar_color is set to background change the titlebar
|
||||
colors to match the current background color of the active kitty window
|
||||
|
||||
- Add a setting to clear all shortcuts defined up to that point in the config
|
||||
file(s)
|
||||
|
||||
- Add a setting (kitty_mod) to change the modifier used by all the default
|
||||
kitty shortcuts, globally
|
||||
|
||||
- Fix Shift+function key not working
|
||||
|
||||
- Support the F13 to F25 function keys
|
||||
|
||||
- Don't fail to start if the user deletes the hintstyle key from their
|
||||
fontconfig configuration.
|
||||
|
||||
- When rendering a private use unicode codepoint and a space as a two cell
|
||||
ligature, set the foreground colors of the space cell to match the colors of
|
||||
the first cell. Works around applications like powerline that use different
|
||||
colors for the two cells.
|
||||
|
||||
- Fix passing @text to other programs such as when viewing the scrollback
|
||||
buffer not working correctly if kitty is itself scrolled up.
|
||||
|
||||
- Fix window focus gained/lost events not being reported to child programs when
|
||||
switching windows/tabs using the various keyboard shortcuts.
|
||||
|
||||
- Fix tab title not changing to reflect the window title when switching between different windows in a tab
|
||||
|
||||
- Ignore -e if it is specified on the command line. This is for compatibility
|
||||
with broken software that assumes terminals should run with an -e option to
|
||||
execute commands instead of just passing the commands as arguments.
|
||||
|
||||
|
||||
version 0.9.0 [2018-04-15]
|
||||
------------------------------
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
:sc_seventh_window: pass:quotes[`ctrl+shift+7`]
|
||||
:sc_show_scrollback: pass:quotes[`ctrl+shift+h`]
|
||||
:sc_sixth_window: pass:quotes[`ctrl+shift+6`]
|
||||
:sc_start_resizing_window: pass:quotes[`ctrl+shift+r`]
|
||||
:sc_tenth_window: pass:quotes[`ctrl+shift+0`]
|
||||
:sc_third_window: pass:quotes[`ctrl+shift+3`]
|
||||
:sc_toggle_fullscreen: pass:quotes[`ctrl+shift+f11`]
|
||||
@@ -73,7 +74,7 @@ link:#layouts[layouts] without needing to use an extra program like tmux
|
||||
* Can be link:remote-control.asciidoc[controlled from scripts or the shell prompt], even over SSH.
|
||||
|
||||
* Has a framework for _kittens_, small terminal programs that can be used to extend kitty's functionality.
|
||||
For example, they are used for link:#unicode-input[Unicode input] and link:#url-hints[Hints].
|
||||
For example, they are used for link:#unicode-input[Unicode input] and link:#hints[Hints].
|
||||
|
||||
* Supports link:#startup-sessions[startup sessions] which allow you to specify the window/tab layout,
|
||||
working directories and programs to run on startup.
|
||||
@@ -87,7 +88,7 @@ window using arbitrary programs of your choice. This is useful for browsing
|
||||
the history comfortably in a pager or editor.
|
||||
|
||||
|
||||
image::screenshot.png?raw=true[Screenshot, showing three programs in the "Tall" layout]
|
||||
image::screenshots/screenshot.png?raw=true[Screenshot, showing three programs in the "Tall" layout]
|
||||
|
||||
toc::[]
|
||||
|
||||
@@ -106,9 +107,10 @@ the following dependencies are installed first.
|
||||
* freetype (not needed on macOS)
|
||||
* fontconfig (not needed on macOS)
|
||||
* ImageMagick (optional, needed to use the `kitty icat` tool to display images in the terminal)
|
||||
* pygments (optional, need for syntax highlighting in `kitty +kitten diff`)
|
||||
* gcc or clang (required only for building)
|
||||
* pkg-config (required only for building)
|
||||
* For building on Linux in addition to the above dependencies you might also need to install the `-dev` packages for `xcursor`, `xrandr`, `xinerama`, `libgl1-mesa` and `xkbcommon-x11`, if they are not already installed by your distro.
|
||||
* For building on Linux in addition to the above dependencies you might also need to install the `-dev` packages for `xcursor`, `xrandr`, `libxi`, `xinerama`, `libgl1-mesa` and `xkbcommon-x11`, if they are not already installed by your distro.
|
||||
|
||||
=== Install and run from source
|
||||
|
||||
@@ -116,7 +118,7 @@ the following dependencies are installed first.
|
||||
git clone https://github.com/kovidgoyal/kitty && cd kitty
|
||||
....
|
||||
|
||||
Now build the C parts of kitty with the following command:
|
||||
Now build the native code parts of kitty with the following command:
|
||||
|
||||
....
|
||||
make
|
||||
@@ -128,11 +130,23 @@ You can run kitty, as:
|
||||
python3 .
|
||||
....
|
||||
|
||||
If that works, you can create a script to launch kitty:
|
||||
|
||||
....
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import runpy
|
||||
runpy.run_path('/path/to/kitty/dir', run_name='__main__')
|
||||
....
|
||||
|
||||
And place it in `~/bin` or `/usr/bin` so that you can run kitty using
|
||||
just `kitty`.
|
||||
|
||||
=== Linux packages
|
||||
|
||||
* Arch Linux: AUR git package https://aur.archlinux.org/packages/kitty-git/
|
||||
* Arch Linux: https://www.archlinux.org/packages/community/x86_64/kitty/
|
||||
|
||||
* Arch Linux: AUR stable package https://aur.archlinux.org/packages/kitty/
|
||||
* Arch Linux: AUR git package https://aur.archlinux.org/packages/kitty-git/
|
||||
|
||||
* NixOS / nixpkgs: https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix
|
||||
|
||||
@@ -140,6 +154,8 @@ python3 .
|
||||
|
||||
* openSUSE: https://build.opensuse.org/package/show/X11:terminals/kitty
|
||||
|
||||
* Debian: https://packages.qa.debian.org/k/kitty/news/20180419T070013Z.html
|
||||
|
||||
=== macOS packages
|
||||
|
||||
kitty is available as a macOS `dmg` file for easy installation from the
|
||||
@@ -278,7 +294,7 @@ the scrollback pager comfortably within the pager.
|
||||
You can input unicode characters by name, hex code, recently used and even an editable favorites list.
|
||||
Press {sc_input_unicode_character} to start the unicode input widget, shown below.
|
||||
|
||||
image::unicode.png?raw=true[Unicode input widget, showing selection of unicode characters by name]
|
||||
image::screenshots/unicode.png?raw=true[Unicode input widget, showing selection of unicode characters by name]
|
||||
|
||||
In Code mode, you enter a unicode character by typing in the hex code for the
|
||||
character and pressing enter, for example, type in 2716 and press enter to get
|
||||
@@ -297,7 +313,7 @@ visible on the screen. For example, you can press {sc_run_kitten_text_hints}
|
||||
to choose any URL visible on the screen and then open it using your system
|
||||
browser.
|
||||
|
||||
image::hints_mode.png?raw=true[URL hints mode]
|
||||
image::screenshots/hints_mode.png?raw=true[URL hints mode]
|
||||
|
||||
Similarly, you can press {sc_run_kitten_text_hints_type_path_program} to
|
||||
select anything that looks like a path or filename and then insert it into the
|
||||
@@ -320,6 +336,7 @@ Currently, there are five layouts available,
|
||||
|
||||
* Stack -- Only a single maximized window is shown at a time
|
||||
* Tall -- One window is shown full height on the left, the rest of the windows are shown one below the other on the right
|
||||
* Fat -- One window is shown full width on the top, the rest of the windows are shown side-by-side on the bottom
|
||||
* Grid -- All windows are shown in a grid
|
||||
* Horizontal -- All windows are shown side-by-side
|
||||
* Vertical -- All windows are shown one below the other
|
||||
@@ -328,7 +345,25 @@ You can switch between layouts using the {sc_next_layout} key combination. You c
|
||||
also create shortcuts to select particular layouts, and choose which layouts
|
||||
you want to enable/disable, see link:kitty/kitty.conf[kitty.conf] for examples.
|
||||
|
||||
Writing a new layout only requires about fifty lines of code, so if there is
|
||||
You can resize windows inside layouts. Press {sc_start_resizing_window} to
|
||||
enter resizing mode and follow the on-screen instructions. In a given window
|
||||
layout only some operations may be possible for a particular window. For
|
||||
example, in the Tall layout you can make the first window wider/narrower, but
|
||||
not taller/shorter. Note that what you are resizing is actually not a window,
|
||||
but a row/column in the layout, all windows in that row/column will be resized.
|
||||
|
||||
Some layouts take options to control their behavior. For example, the `fat` and `tall`
|
||||
layouts accept the `bias` option to control how the available space is split up. To specify the
|
||||
option, in kitty.conf use:
|
||||
|
||||
```
|
||||
enabled_layouts tall:bias=70
|
||||
```
|
||||
|
||||
This will make the tall window occupy `70%` of available width. `bias` can be
|
||||
any number between 10 and 90.
|
||||
|
||||
Writing a new layout only requires about a hundred lines of code, so if there is
|
||||
some layout you want, take a look at link:kitty/layout.py[layout.py] and submit
|
||||
a pull request!
|
||||
|
||||
@@ -536,6 +571,17 @@ Really, the correct solution for this is to convince the OpenSSH maintainers to
|
||||
have ssh do this automatically when connecting to a server, so that all
|
||||
terminals work transparently.
|
||||
|
||||
In the meantime, you can automate it by using a simple script called, for example,
|
||||
`~/bin/myssh`
|
||||
|
||||
....
|
||||
#!/bin/sh
|
||||
infocmp xterm-kitty | ssh $1 tic -x -o \~/.terminfo /dev/stdin && ssh $1
|
||||
....
|
||||
|
||||
Then you can use `myssh server` to log in to `server` with the terminfo file
|
||||
being automatically available.
|
||||
|
||||
|
||||
=== How do I change the colors in a running kitty instance?
|
||||
|
||||
@@ -559,6 +605,20 @@ You can, of course, also run kitty from a terminal with command line options, us
|
||||
And within kitty itself, you can always run kitty using just `kitty` as it
|
||||
cleverly adds itself to the PATH.
|
||||
|
||||
|
||||
=== kitty is not able to use my favorite font?
|
||||
|
||||
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.
|
||||
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
|
||||
your font is not listed in `kitty list-fonts` it means that it is not
|
||||
monospace. On Linux you can list all monospace fonts with,
|
||||
|
||||
```
|
||||
fc-list : family spacing | grep spacing=100
|
||||
```
|
||||
|
||||
== Resources on terminal behavior
|
||||
|
||||
http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
|
||||
@@ -6,7 +6,7 @@ import sys
|
||||
|
||||
|
||||
def icat(args):
|
||||
from kitty.icat import main
|
||||
from kittens.icat.main import main
|
||||
main(args)
|
||||
|
||||
|
||||
|
||||
134
gen-wcwidth.py
134
gen-wcwidth.py
@@ -44,7 +44,8 @@ def parse_ucd():
|
||||
def add_word(w, c):
|
||||
if c <= 32 or c == 127 or 128 <= c <= 159:
|
||||
return
|
||||
word_search_map[w.lower()].add(c)
|
||||
if len(w) > 1:
|
||||
word_search_map[w.lower()].add(c)
|
||||
|
||||
first = None
|
||||
for word, c in html5.items():
|
||||
@@ -54,7 +55,9 @@ def parse_ucd():
|
||||
for line in get_data('ucd/UnicodeData.txt'):
|
||||
parts = [x.strip() for x in line.split(';')]
|
||||
codepoint = int(parts[0], 16)
|
||||
name = parts[1]
|
||||
name = parts[1] or parts[10]
|
||||
if name == '<control>':
|
||||
name = parts[10]
|
||||
if name:
|
||||
name_map[codepoint] = name
|
||||
for word in name.lower().split():
|
||||
@@ -207,7 +210,8 @@ def codepoint_to_mark_map(p, mark_map):
|
||||
for spec in get_ranges(mark_map):
|
||||
if isinstance(spec, tuple):
|
||||
s = rmap[spec[0]]
|
||||
p(f'\t\tcase {spec[0]} ... {spec[1]}: return {s} + c - {spec[0]};')
|
||||
cases = ' '.join(f'case {i}:' for i in range(spec[0], spec[1]+1))
|
||||
p(f'\t\t{cases} return {s} + c - {spec[0]};')
|
||||
else:
|
||||
p(f'\t\tcase {spec}: return {rmap[spec]};')
|
||||
p('default: return 0;')
|
||||
@@ -215,10 +219,12 @@ def codepoint_to_mark_map(p, mark_map):
|
||||
return rmap
|
||||
|
||||
|
||||
def classes_to_regex(classes):
|
||||
def classes_to_regex(classes, exclude=''):
|
||||
chars = set()
|
||||
for c in classes:
|
||||
chars |= class_maps[c]
|
||||
for c in map(ord, exclude):
|
||||
chars.discard(c)
|
||||
|
||||
def as_string(codepoint):
|
||||
if codepoint < 256:
|
||||
@@ -256,53 +262,111 @@ def gen_ucd():
|
||||
raise ValueError('The mark for 0xfe0e has changed, you have to update VS15 to {} and VS16 to {} in unicode-data.h'.format(
|
||||
rmap[0xfe0e], rmap[0xfe0f]
|
||||
))
|
||||
with open('kittens/url_hints/url_regex.py', 'w') as f:
|
||||
f.write("url_delimiters = '{}' # noqa".format(''.join(classes_to_regex(cz))))
|
||||
with open('kittens/hints/url_regex.py', 'w') as f:
|
||||
f.write("url_delimiters = '{}' # noqa".format(''.join(classes_to_regex(cz, exclude='\n'))))
|
||||
|
||||
|
||||
def gen_names():
|
||||
words = tuple(sorted(word_search_map))
|
||||
|
||||
with create_header('kittens/unicode_input/names.h') as p:
|
||||
cp_map = list(sorted(name_map))
|
||||
p(f'static const char* name_map[{len(cp_map)}] = {{' ' // {{{')
|
||||
for cp in cp_map:
|
||||
mark_to_cp = list(sorted(name_map))
|
||||
cp_to_mark = {cp: m for m, cp in enumerate(mark_to_cp)}
|
||||
# Mapping of mark to codepoint name
|
||||
p(f'static const char* name_map[{len(mark_to_cp)}] = {{' ' // {{{')
|
||||
for cp in mark_to_cp:
|
||||
w = name_map[cp].replace('"', '\\"')
|
||||
p(f'\t"{w}",')
|
||||
p("}; // }}}\n")
|
||||
|
||||
p(f'static const char* idx_to_word[{len(words)}] = ' '{ // {{{')
|
||||
for s in words:
|
||||
s = s.replace('"', '\\"')
|
||||
p(f'\t"{s}",')
|
||||
p("}; // }}}\n")
|
||||
|
||||
first_letters = {ord(w[0]) for w in words if ord(w[0]) < 256}
|
||||
wmap = {w: i for i, w in enumerate(words)}
|
||||
p(f'static const unsigned short* words_for_first_letter[256] = ' '{ // {{{')
|
||||
for fl in range(0, 256):
|
||||
if fl in first_letters:
|
||||
winds = [str(wmap[w]) for w in words if w.startswith(chr(fl))]
|
||||
p(f'\t(const unsigned short[{len(winds) + 1}]){{{len(winds)}, ', ', '.join(winds), '},')
|
||||
else:
|
||||
p('NULL,')
|
||||
p("}; // }}}\n")
|
||||
|
||||
p(f'static const char_type* codepoints_for_word_idx[{len(words)}] = ' '{ // {{{')
|
||||
for s in words:
|
||||
cps = word_search_map[s]
|
||||
a = ', '.join(map(str, cps))
|
||||
p(f'\t(const char_type[{len(cps) + 1}]){{{len(cps)}, ', a, '},')
|
||||
p("}; // }}}\n")
|
||||
# Mapping of mark to codepoint
|
||||
p(f'static const char_type mark_to_cp[{len(mark_to_cp)}] = {{' ' // {{{')
|
||||
p(', '.join(map(str, mark_to_cp)))
|
||||
p('}; // }}}\n')
|
||||
|
||||
# Function to get mark number for codepoint
|
||||
p('static char_type mark_for_codepoint(char_type c) {')
|
||||
codepoint_to_mark_map(p, cp_map)
|
||||
codepoint_to_mark_map(p, mark_to_cp)
|
||||
p('}\n')
|
||||
p('static inline const char* name_for_codepoint(char_type cp) {')
|
||||
p('\tchar_type m = mark_for_codepoint(cp); if (m == 0) return NULL;')
|
||||
p('\treturn name_map[m];')
|
||||
p('}\n')
|
||||
|
||||
# Array of all words
|
||||
word_map = tuple(sorted(word_search_map))
|
||||
word_rmap = {w: i for i, w in enumerate(word_map)}
|
||||
p(f'static const char* all_words_map[{len(word_map)}] = {{' ' // {{{')
|
||||
cwords = (w.replace('"', '\\"') for w in word_map)
|
||||
p(', '.join(f'"{w}"' for w in cwords))
|
||||
p('}; // }}}\n')
|
||||
|
||||
# Array of sets of marks for each word
|
||||
word_to_marks = {word_rmap[w]: frozenset(map(cp_to_mark.__getitem__, cps)) for w, cps in word_search_map.items()}
|
||||
all_mark_groups = frozenset(word_to_marks.values())
|
||||
array = [0]
|
||||
mg_to_offset = {}
|
||||
for mg in all_mark_groups:
|
||||
mg_to_offset[mg] = len(array)
|
||||
array.append(len(mg))
|
||||
array.extend(sorted(mg))
|
||||
p(f'static const char_type mark_groups[{len(array)}] = {{' ' // {{{')
|
||||
p(', '.join(map(str, array)))
|
||||
p('}; // }}}\n')
|
||||
offsets_array = []
|
||||
for wi, w in enumerate(word_map):
|
||||
mg = word_to_marks[wi]
|
||||
offsets_array.append(mg_to_offset[mg])
|
||||
p(f'static const char_type mark_to_offset[{len(offsets_array)}] = {{' ' // {{{')
|
||||
p(', '.join(map(str, offsets_array)))
|
||||
p('}; // }}}\n')
|
||||
|
||||
# The trie
|
||||
p(f'typedef struct {{ uint32_t children_offset; uint32_t match_offset; }} word_trie;\n')
|
||||
all_trie_nodes = []
|
||||
|
||||
class TrieNode:
|
||||
|
||||
def __init__(self):
|
||||
self.match_offset = 0
|
||||
self.children_offset = 0
|
||||
self.children = {}
|
||||
|
||||
def add_letter(self, letter):
|
||||
if letter not in self.children:
|
||||
self.children[letter] = len(all_trie_nodes)
|
||||
all_trie_nodes.append(TrieNode())
|
||||
return self.children[letter]
|
||||
|
||||
def __str__(self):
|
||||
return f'{{ .children_offset={self.children_offset}, .match_offset={self.match_offset} }}'
|
||||
|
||||
root = TrieNode()
|
||||
all_trie_nodes.append(root)
|
||||
|
||||
def add_word(word_idx):
|
||||
word = word_map[word_idx]
|
||||
parent = root
|
||||
for letter in map(ord, word):
|
||||
idx = parent.add_letter(letter)
|
||||
parent = all_trie_nodes[idx]
|
||||
parent.match_offset = offsets_array[word_idx]
|
||||
|
||||
for i in range(len(word_map)):
|
||||
add_word(i)
|
||||
children_array = [0]
|
||||
for node in all_trie_nodes:
|
||||
if node.children:
|
||||
node.children_offset = len(children_array)
|
||||
children_array.append(len(node.children))
|
||||
for letter, child_offset in node.children.items():
|
||||
children_array.append((child_offset << 8) | (letter & 0xff))
|
||||
|
||||
p(f'static const word_trie all_trie_nodes[{len(all_trie_nodes)}] = {{' ' // {{{')
|
||||
p(',\n'.join(map(str, all_trie_nodes)))
|
||||
p('\n}; // }}}\n')
|
||||
p(f'static const uint32_t children_array[{len(children_array)}] = {{' ' // {{{')
|
||||
p(', '.join(map(str, children_array)))
|
||||
p('}; // }}}\n')
|
||||
|
||||
|
||||
def gen_wcwidth():
|
||||
seen = set()
|
||||
|
||||
@@ -27,6 +27,10 @@ def regenerate():
|
||||
)
|
||||
|
||||
|
||||
def strip_trailing_whitespace(c):
|
||||
return re.sub(r'\s+$', '', c, flags=re.MULTILINE) + '\n'
|
||||
|
||||
|
||||
def export():
|
||||
c = open('out/src/glad.c', 'rb').read().decode('utf-8')
|
||||
functions = []
|
||||
@@ -48,12 +52,13 @@ init_glad(GLADloadproc load, int debug) {
|
||||
}
|
||||
return ret;
|
||||
}'''.replace('SUB', ';\n '.join(switch), 1)
|
||||
open('../kitty/gl-wrapper.c', 'w').write(c)
|
||||
open('../kitty/gl-wrapper.c', 'w').write(strip_trailing_whitespace(c))
|
||||
raw = open('out/include/glad/glad.h').read()
|
||||
raw = raw.replace('<KHR/khrplatform.h>', '"khrplatform.h"')
|
||||
raw += '\nint init_glad(GLADloadproc, int);\n'
|
||||
open('../kitty/gl-wrapper.h', 'w').write(raw)
|
||||
shutil.copy2('out/include/KHR/khrplatform.h', '../kitty')
|
||||
open('../kitty/gl-wrapper.h', 'w').write(strip_trailing_whitespace(raw))
|
||||
raw = open('out/include/KHR/khrplatform.h', 'rb').read().decode('utf-8')
|
||||
open('../kitty/khrplatform.h', 'w').write(strip_trailing_whitespace(raw))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -208,6 +208,9 @@ static GLFWbool updateUnicodeDataNS(void)
|
||||
_glfw.ns.unicodeData = nil;
|
||||
}
|
||||
|
||||
for (_GLFWwindow *window = _glfw.windowListHead; window; window = window->next)
|
||||
window->ns.deadKeyState = 0;
|
||||
|
||||
_glfw.ns.inputSource = TISCopyCurrentKeyboardLayoutInputSource();
|
||||
if (!_glfw.ns.inputSource)
|
||||
{
|
||||
|
||||
5
glfw/cocoa_platform.h
vendored
5
glfw/cocoa_platform.h
vendored
@@ -37,6 +37,7 @@ typedef void* id;
|
||||
#endif
|
||||
|
||||
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,int);
|
||||
|
||||
typedef struct VkMacOSSurfaceCreateInfoMVK
|
||||
{
|
||||
@@ -98,6 +99,10 @@ typedef struct _GLFWwindowNS
|
||||
// This is kept to counteract Cocoa doing the same internally
|
||||
double cursorWarpDeltaX, cursorWarpDeltaY;
|
||||
|
||||
// The text input filter callback
|
||||
GLFWcocoatextinputfilterfun textInputFilterCallback;
|
||||
// Dead key state
|
||||
UInt32 deadKeyState;
|
||||
} _GLFWwindowNS;
|
||||
|
||||
// Cocoa-specific global data
|
||||
|
||||
@@ -128,6 +128,32 @@ static void updateCursorImage(_GLFWwindow* window)
|
||||
hideCursor(window);
|
||||
}
|
||||
|
||||
// Apply chosen cursor mode to a focused window
|
||||
//
|
||||
static void updateCursorMode(_GLFWwindow* window)
|
||||
{
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
{
|
||||
_glfw.ns.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.ns.restoreCursorPosX,
|
||||
&_glfw.ns.restoreCursorPosY);
|
||||
centerCursor(window);
|
||||
CGAssociateMouseAndMouseCursorPosition(false);
|
||||
}
|
||||
else if (_glfw.ns.disabledCursorWindow == window)
|
||||
{
|
||||
_glfw.ns.disabledCursorWindow = NULL;
|
||||
CGAssociateMouseAndMouseCursorPosition(true);
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.ns.restoreCursorPosX,
|
||||
_glfw.ns.restoreCursorPosY);
|
||||
}
|
||||
|
||||
if (cursorInClientArea(window))
|
||||
updateCursorImage(window);
|
||||
}
|
||||
|
||||
// Transforms the specified y-coordinate between the CG display and NS screen
|
||||
// coordinate systems
|
||||
//
|
||||
@@ -423,7 +449,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
centerCursor(window);
|
||||
|
||||
_glfwInputWindowFocus(window, GLFW_TRUE);
|
||||
_glfwPlatformSetCursorMode(window, window->cursorMode);
|
||||
updateCursorMode(window);
|
||||
}
|
||||
|
||||
- (void)windowDidResignKey:(NSNotification *)notification
|
||||
@@ -719,18 +745,98 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
[super updateTrackingAreas];
|
||||
}
|
||||
|
||||
static inline UInt32
|
||||
convert_cocoa_to_carbon_modifiers(NSUInteger flags) {
|
||||
UInt32 mods = 0;
|
||||
if (flags & NSEventModifierFlagShift)
|
||||
mods |= shiftKey;
|
||||
if (flags & NSEventModifierFlagControl)
|
||||
mods |= controlKey;
|
||||
if (flags & NSEventModifierFlagOption)
|
||||
mods |= optionKey;
|
||||
if (flags & NSEventModifierFlagCommand)
|
||||
mods |= cmdKey;
|
||||
if (flags & NSEventModifierFlagCapsLock)
|
||||
mods |= alphaLock;
|
||||
|
||||
return (mods >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
static inline void
|
||||
convert_utf16_to_utf8(UniChar *src, UniCharCount src_length, char *dest, size_t dest_sz) {
|
||||
CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
|
||||
src,
|
||||
src_length,
|
||||
kCFAllocatorNull);
|
||||
CFStringGetCString(string,
|
||||
dest,
|
||||
dest_sz,
|
||||
kCFStringEncodingUTF8);
|
||||
CFRelease(string);
|
||||
}
|
||||
|
||||
static inline GLFWbool
|
||||
is_ascii_control_char(char x) {
|
||||
return x == 0 || (1 <= x && x <= 31) || x == 127;
|
||||
}
|
||||
|
||||
- (void)keyDown:(NSEvent *)event
|
||||
{
|
||||
const unsigned int scancode = [event keyCode];
|
||||
const NSUInteger flags = [event modifierFlags];
|
||||
const int mods = translateFlags(flags);
|
||||
const int key = translateKey(scancode, GLFW_TRUE);
|
||||
const int mods = translateFlags([event modifierFlags]);
|
||||
const GLFWbool process_text = !window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, scancode) != 1;
|
||||
_glfw.ns.text[0] = 0;
|
||||
// this will call insertText with the text for this event, if any
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
if ((1 <= _glfw.ns.text[0] && _glfw.ns.text[0] <= 31) || (unsigned)_glfw.ns.text[0] == 127) _glfw.ns.text[0] = 0; // dont send text for ascii control codes
|
||||
debug_key(@"scancode: 0x%x (%s) mods: %stext: %s glfw_key: %s\n",
|
||||
if (!_glfw.ns.unicodeData) {
|
||||
// Using the cocoa API for key handling is disabled, as there is no
|
||||
// reliable way to handle dead keys using it. Only use it if the
|
||||
// keyboard unicode data is not available.
|
||||
if (process_text) {
|
||||
// this will call insertText with the text for this event, if any
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
}
|
||||
} else {
|
||||
static UniChar text[256];
|
||||
UniCharCount char_count = 0;
|
||||
if (UCKeyTranslate(
|
||||
[(NSData*) _glfw.ns.unicodeData bytes],
|
||||
scancode,
|
||||
kUCKeyActionDown,
|
||||
convert_cocoa_to_carbon_modifiers(flags),
|
||||
LMGetKbdType(),
|
||||
(process_text ? 0 : kUCKeyTranslateNoDeadKeysMask),
|
||||
&(window->ns.deadKeyState),
|
||||
sizeof(text)/sizeof(text[0]),
|
||||
&char_count,
|
||||
text
|
||||
) != noErr) {
|
||||
debug_key(@"UCKeyTranslate failed for scancode: 0x%x (%s) %s\n", scancode, safe_name_for_scancode(scancode), format_mods(mods));
|
||||
window->ns.deadKeyState = 0;
|
||||
return;
|
||||
}
|
||||
if (process_text) {
|
||||
// We check if cocoa wants to insert text, as UCKeyTranslate
|
||||
// inserts text even when the cmd key is pressed. For instance,
|
||||
// cmd+a will result in the text a.
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
debug_key(@"char_count: %lu cocoa text: %s\n", char_count, format_text(_glfw.ns.text));
|
||||
GLFWbool cocoa_wants_to_insert_text = !is_ascii_control_char(_glfw.ns.text[0]);
|
||||
_glfw.ns.text[0] = 0;
|
||||
if (char_count && cocoa_wants_to_insert_text) convert_utf16_to_utf8(text, char_count, _glfw.ns.text, sizeof(_glfw.ns.text));
|
||||
} else {
|
||||
window->ns.deadKeyState = 0;
|
||||
}
|
||||
if (window->ns.deadKeyState && char_count == 0) {
|
||||
debug_key(@"Ignoring dead key. deadKeyState: 0x%x scancode: 0x%x (%s) %s\n",
|
||||
window->ns.deadKeyState, scancode, safe_name_for_scancode(scancode), format_mods(mods));
|
||||
return;
|
||||
}
|
||||
}
|
||||
debug_key(@"scancode: 0x%x (%s) %stext: %s glfw_key: %s\n",
|
||||
scancode, safe_name_for_scancode(scancode), format_mods(mods),
|
||||
format_text(_glfw.ns.text), _glfwGetKeyName(key));
|
||||
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // dont send text for ascii control codes
|
||||
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
|
||||
}
|
||||
|
||||
@@ -1233,6 +1339,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
const _GLFWctxconfig* ctxconfig,
|
||||
const _GLFWfbconfig* fbconfig)
|
||||
{
|
||||
window->ns.deadKeyState = 0;
|
||||
if (!initializeAppKit())
|
||||
return GLFW_FALSE;
|
||||
|
||||
@@ -1732,26 +1839,8 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
|
||||
|
||||
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
||||
{
|
||||
if (mode == GLFW_CURSOR_DISABLED)
|
||||
{
|
||||
_glfw.ns.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.ns.restoreCursorPosX,
|
||||
&_glfw.ns.restoreCursorPosY);
|
||||
centerCursor(window);
|
||||
CGAssociateMouseAndMouseCursorPosition(false);
|
||||
}
|
||||
else if (_glfw.ns.disabledCursorWindow == window)
|
||||
{
|
||||
_glfw.ns.disabledCursorWindow = NULL;
|
||||
CGAssociateMouseAndMouseCursorPosition(true);
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.ns.restoreCursorPosX,
|
||||
_glfw.ns.restoreCursorPosY);
|
||||
}
|
||||
|
||||
if (cursorInClientArea(window))
|
||||
updateCursorImage(window);
|
||||
if (_glfwPlatformWindowFocused(window))
|
||||
updateCursorMode(window);
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetScancodeName(int scancode)
|
||||
@@ -1777,16 +1866,7 @@ const char* _glfwPlatformGetScancodeName(int scancode)
|
||||
if (!characterCount)
|
||||
return NULL;
|
||||
|
||||
CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
|
||||
characters,
|
||||
characterCount,
|
||||
kCFAllocatorNull);
|
||||
CFStringGetCString(string,
|
||||
_glfw.ns.keyName,
|
||||
sizeof(_glfw.ns.keyName),
|
||||
kCFStringEncodingUTF8);
|
||||
CFRelease(string);
|
||||
|
||||
convert_utf16_to_utf8(characters, characterCount, _glfw.ns.keyName, sizeof(_glfw.ns.keyName));
|
||||
return _glfw.ns.keyName;
|
||||
}
|
||||
|
||||
@@ -2000,3 +2080,11 @@ GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
||||
return window->ns.object;
|
||||
}
|
||||
|
||||
GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *handle, GLFWcocoatextinputfilterfun callback) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
||||
GLFWcocoatextinputfilterfun previous = window->ns.textInputFilterCallback;
|
||||
window->ns.textInputFilterCallback = callback;
|
||||
return previous;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
at_least_version('xkbcommon', 0, 5)
|
||||
|
||||
if module == 'x11':
|
||||
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11'.split():
|
||||
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb'.split():
|
||||
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
||||
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
||||
|
||||
@@ -101,7 +101,7 @@ def collect_source_information():
|
||||
).group(1).strip().split()
|
||||
)
|
||||
|
||||
wayland_protocols = re.search('WaylandProtocols\s+(\S+)\s+', mraw).group(1)
|
||||
wayland_protocols = re.search(r'WaylandProtocols\s+(\S+)\s+', mraw).group(1)
|
||||
wayland_protocols = list(map(int, wayland_protocols.split('.')))
|
||||
ans = {
|
||||
'common': dict(extract_sources('common')),
|
||||
@@ -196,6 +196,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
||||
for line in '''\
|
||||
void* glfwGetCocoaWindow(GLFWwindow* window)
|
||||
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
||||
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
||||
void* glfwGetX11Display(void)
|
||||
int32_t glfwGetX11Window(GLFWwindow* window)
|
||||
void glfwSetX11SelectionString(const char* string)
|
||||
@@ -212,6 +213,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
||||
{}
|
||||
|
||||
{}
|
||||
|
||||
4
glfw/glfw3.h
vendored
4
glfw/glfw3.h
vendored
@@ -4582,7 +4582,7 @@ GLFWAPI const char* glfwGetJoystickGUID(int jid);
|
||||
* This function may be called from the joystick callback, even for a joystick
|
||||
* that is being disconnected.
|
||||
*
|
||||
* @param[in] joystick The joystick whose pointer to set.
|
||||
* @param[in] jid The joystick whose pointer to set.
|
||||
* @param[in] pointer The new value.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
@@ -4607,7 +4607,7 @@ GLFWAPI void glfwSetJoystickUserPointer(int jid, void* pointer);
|
||||
* This function may be called from the joystick callback, even for a joystick
|
||||
* that is being disconnected.
|
||||
*
|
||||
* @param[in] joystick The joystick whose pointer to return.
|
||||
* @param[in] jid The joystick whose pointer to return.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
|
||||
56
glfw/init.c
vendored
56
glfw/init.c
vendored
@@ -57,37 +57,6 @@ static _GLFWinitconfig _glfwInitHints =
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a generic string representation of the specified error
|
||||
//
|
||||
static const char* getErrorString(int code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case GLFW_NOT_INITIALIZED:
|
||||
return "The GLFW library is not initialized";
|
||||
case GLFW_NO_CURRENT_CONTEXT:
|
||||
return "There is no current context";
|
||||
case GLFW_INVALID_ENUM:
|
||||
return "Invalid argument for enum parameter";
|
||||
case GLFW_INVALID_VALUE:
|
||||
return "Invalid value for parameter";
|
||||
case GLFW_OUT_OF_MEMORY:
|
||||
return "Out of memory";
|
||||
case GLFW_API_UNAVAILABLE:
|
||||
return "The requested API is unavailable";
|
||||
case GLFW_VERSION_UNAVAILABLE:
|
||||
return "The requested API version is unavailable";
|
||||
case GLFW_PLATFORM_ERROR:
|
||||
return "An undocumented platform-specific error occurred";
|
||||
case GLFW_FORMAT_UNAVAILABLE:
|
||||
return "The requested format is unavailable";
|
||||
case GLFW_NO_WINDOW_CONTEXT:
|
||||
return "The specified window has no context";
|
||||
default:
|
||||
return "ERROR: UNKNOWN GLFW ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
// Terminate the library
|
||||
//
|
||||
static void terminate(void)
|
||||
@@ -173,7 +142,30 @@ void _glfwInputError(int code, const char* format, ...)
|
||||
description[sizeof(description) - 1] = '\0';
|
||||
}
|
||||
else
|
||||
strcpy(description, getErrorString(code));
|
||||
{
|
||||
if (code == GLFW_NOT_INITIALIZED)
|
||||
strcpy(description, "The GLFW library is not initialized");
|
||||
else if (code == GLFW_NO_CURRENT_CONTEXT)
|
||||
strcpy(description, "There is no current context");
|
||||
else if (code == GLFW_INVALID_ENUM)
|
||||
strcpy(description, "Invalid argument for enum parameter");
|
||||
else if (code == GLFW_INVALID_VALUE)
|
||||
strcpy(description, "Invalid value for parameter");
|
||||
else if (code == GLFW_OUT_OF_MEMORY)
|
||||
strcpy(description, "Out of memory");
|
||||
else if (code == GLFW_API_UNAVAILABLE)
|
||||
strcpy(description, "The requested API is unavailable");
|
||||
else if (code == GLFW_VERSION_UNAVAILABLE)
|
||||
strcpy(description, "The requested API version is unavailable");
|
||||
else if (code == GLFW_PLATFORM_ERROR)
|
||||
strcpy(description, "A platform-specific error occurred");
|
||||
else if (code == GLFW_FORMAT_UNAVAILABLE)
|
||||
strcpy(description, "The requested format is unavailable");
|
||||
else if (code == GLFW_NO_WINDOW_CONTEXT)
|
||||
strcpy(description, "The specified window has no context");
|
||||
else
|
||||
strcpy(description, "ERROR: UNKNOWN GLFW ERROR");
|
||||
}
|
||||
|
||||
if (_glfw.initialized)
|
||||
{
|
||||
|
||||
11
glfw/input.c
vendored
11
glfw/input.c
vendored
@@ -32,7 +32,6 @@
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
// Internal key state used for sticky keys
|
||||
#define _GLFW_STICK 3
|
||||
@@ -619,11 +618,7 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&window->virtualCursorPosX,
|
||||
&window->virtualCursorPosY);
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (_glfwPlatformWindowFocused(window))
|
||||
#endif
|
||||
_glfwPlatformSetCursorMode(window, value);
|
||||
_glfwPlatformSetCursorMode(window, value);
|
||||
}
|
||||
else if (mode == GLFW_STICKY_KEYS)
|
||||
{
|
||||
@@ -1180,7 +1175,9 @@ GLFWAPI int glfwUpdateGamepadMappings(const char* string)
|
||||
|
||||
while (*c)
|
||||
{
|
||||
if (isxdigit(*c))
|
||||
if ((*c >= '0' && *c <= '9') ||
|
||||
(*c >= 'a' && *c <= 'f') ||
|
||||
(*c >= 'A' && *c <= 'F'))
|
||||
{
|
||||
char line[1024];
|
||||
|
||||
|
||||
6
glfw/win32_platform.h
vendored
6
glfw/win32_platform.h
vendored
@@ -120,6 +120,8 @@ typedef struct
|
||||
HRGN hRgnBlur;
|
||||
BOOL fTransitionOnMaximized;
|
||||
} DWM_BLURBEHIND;
|
||||
#else
|
||||
#include <dwmapi.h>
|
||||
#endif /*Windows Vista*/
|
||||
|
||||
#ifndef DPI_ENUMS_DECLARED
|
||||
@@ -240,7 +242,9 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)(
|
||||
#include "egl_context.h"
|
||||
#include "osmesa_context.h"
|
||||
|
||||
#define _GLFW_WNDCLASSNAME L"GLFW30"
|
||||
#if !defined(_GLFW_WNDCLASSNAME)
|
||||
#define _GLFW_WNDCLASSNAME L"GLFW30"
|
||||
#endif
|
||||
|
||||
#define _glfw_dlopen(name) LoadLibraryA(name)
|
||||
#define _glfw_dlclose(handle) FreeLibrary((HMODULE) handle)
|
||||
|
||||
130
glfw/win32_window.c
vendored
130
glfw/win32_window.c
vendored
@@ -235,26 +235,6 @@ static void centerCursor(_GLFWwindow* window)
|
||||
_glfwPlatformSetCursorPos(window, width / 2.0, height / 2.0);
|
||||
}
|
||||
|
||||
// Returns whether the cursor is in the client area of the specified window
|
||||
//
|
||||
static GLFWbool cursorInClientArea(_GLFWwindow* window)
|
||||
{
|
||||
RECT area;
|
||||
POINT pos;
|
||||
|
||||
if (!GetCursorPos(&pos))
|
||||
return GLFW_FALSE;
|
||||
|
||||
if (WindowFromPoint(pos) != window->win32.handle)
|
||||
return GLFW_FALSE;
|
||||
|
||||
GetClientRect(window->win32.handle, &area);
|
||||
ClientToScreen(window->win32.handle, (POINT*) &area.left);
|
||||
ClientToScreen(window->win32.handle, (POINT*) &area.right);
|
||||
|
||||
return PtInRect(&area, pos);
|
||||
}
|
||||
|
||||
// Updates the cursor image according to its cursor mode
|
||||
//
|
||||
static void updateCursorImage(_GLFWwindow* window)
|
||||
@@ -286,6 +266,67 @@ static void updateClipRect(_GLFWwindow* window)
|
||||
ClipCursor(NULL);
|
||||
}
|
||||
|
||||
// Apply disabled cursor mode to a focused window
|
||||
//
|
||||
static void disableCursor(_GLFWwindow* window)
|
||||
{
|
||||
const RAWINPUTDEVICE rid = { 0x01, 0x02, 0, window->win32.handle };
|
||||
|
||||
_glfw.win32.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.win32.restoreCursorPosX,
|
||||
&_glfw.win32.restoreCursorPosY);
|
||||
updateCursorImage(window);
|
||||
centerCursor(window);
|
||||
updateClipRect(window);
|
||||
|
||||
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"Win32: Failed to register raw input device");
|
||||
}
|
||||
}
|
||||
|
||||
// Exit disabled cursor mode for the specified window
|
||||
//
|
||||
static void enableCursor(_GLFWwindow* window)
|
||||
{
|
||||
const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL };
|
||||
|
||||
_glfw.win32.disabledCursorWindow = NULL;
|
||||
updateClipRect(NULL);
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.win32.restoreCursorPosX,
|
||||
_glfw.win32.restoreCursorPosY);
|
||||
updateCursorImage(window);
|
||||
|
||||
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"Win32: Failed to remove raw input device");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns whether the cursor is in the client area of the specified window
|
||||
//
|
||||
static GLFWbool cursorInClientArea(_GLFWwindow* window)
|
||||
{
|
||||
RECT area;
|
||||
POINT pos;
|
||||
|
||||
if (!GetCursorPos(&pos))
|
||||
return GLFW_FALSE;
|
||||
|
||||
if (WindowFromPoint(pos) != window->win32.handle)
|
||||
return GLFW_FALSE;
|
||||
|
||||
GetClientRect(window->win32.handle, &area);
|
||||
ClientToScreen(window->win32.handle, (POINT*) &area.left);
|
||||
ClientToScreen(window->win32.handle, (POINT*) &area.right);
|
||||
|
||||
return PtInRect(&area, pos);
|
||||
}
|
||||
|
||||
// Update native window styles to match attributes
|
||||
//
|
||||
static void updateWindowStyles(const _GLFWwindow* window)
|
||||
@@ -575,7 +616,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
if (lParam == 0 && window->win32.frameAction)
|
||||
{
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED);
|
||||
disableCursor(window);
|
||||
|
||||
window->win32.frameAction = GLFW_FALSE;
|
||||
}
|
||||
@@ -593,7 +634,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
break;
|
||||
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED);
|
||||
disableCursor(window);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -601,7 +642,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
case WM_KILLFOCUS:
|
||||
{
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_NORMAL);
|
||||
enableCursor(window);
|
||||
|
||||
if (window->monitor && window->autoIconify)
|
||||
_glfwPlatformIconifyWindow(window);
|
||||
@@ -857,10 +898,10 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
case WM_ENTERSIZEMOVE:
|
||||
case WM_ENTERMENULOOP:
|
||||
{
|
||||
// HACK: Postpone cursor disabling while the user is moving or
|
||||
// resizing the window or using the menu
|
||||
// HACK: Enable the cursor while the user is moving or
|
||||
// resizing the window or using the window menu
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_NORMAL);
|
||||
enableCursor(window);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -871,7 +912,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
// HACK: Disable the cursor once the user is done moving or
|
||||
// resizing the window or using the menu
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED);
|
||||
disableCursor(window);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -1777,39 +1818,12 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
||||
{
|
||||
if (mode == GLFW_CURSOR_DISABLED)
|
||||
{
|
||||
const RAWINPUTDEVICE rid = { 0x01, 0x02, 0, window->win32.handle };
|
||||
|
||||
_glfw.win32.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.win32.restoreCursorPosX,
|
||||
&_glfw.win32.restoreCursorPosY);
|
||||
centerCursor(window);
|
||||
updateClipRect(window);
|
||||
|
||||
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"Win32: Failed to register raw input device");
|
||||
}
|
||||
if (_glfwPlatformWindowFocused(window))
|
||||
disableCursor(window);
|
||||
}
|
||||
else if (_glfw.win32.disabledCursorWindow == window)
|
||||
{
|
||||
const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL };
|
||||
|
||||
_glfw.win32.disabledCursorWindow = NULL;
|
||||
updateClipRect(NULL);
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.win32.restoreCursorPosX,
|
||||
_glfw.win32.restoreCursorPosY);
|
||||
|
||||
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"Win32: Failed to remove raw input device");
|
||||
}
|
||||
}
|
||||
|
||||
if (cursorInClientArea(window))
|
||||
enableCursor(window);
|
||||
else if (cursorInClientArea(window))
|
||||
updateCursorImage(window);
|
||||
}
|
||||
|
||||
|
||||
53
glfw/x11_init.c
vendored
53
glfw/x11_init.c
vendored
@@ -166,7 +166,11 @@ static GLFWbool initExtensions(void)
|
||||
&_glfw.x11.vidmode.errorBase);
|
||||
}
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
_glfw.x11.xi.handle = _glfw_dlopen("libXi-6.so");
|
||||
#else
|
||||
_glfw.x11.xi.handle = _glfw_dlopen("libXi.so.6");
|
||||
#endif
|
||||
if (_glfw.x11.xi.handle)
|
||||
{
|
||||
_glfw.x11.xi.QueryVersion = (PFN_XIQueryVersion)
|
||||
@@ -192,7 +196,11 @@ static GLFWbool initExtensions(void)
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
_glfw.x11.randr.handle = _glfw_dlopen("libXrandr-2.so");
|
||||
#else
|
||||
_glfw.x11.randr.handle = _glfw_dlopen("libXrandr.so.2");
|
||||
#endif
|
||||
if (_glfw.x11.randr.handle)
|
||||
{
|
||||
_glfw.x11.randr.AllocGamma = (PFN_XRRAllocGamma)
|
||||
@@ -280,7 +288,11 @@ static GLFWbool initExtensions(void)
|
||||
RROutputChangeNotifyMask);
|
||||
}
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
_glfw.x11.xcursor.handle = _glfw_dlopen("libXcursor-1.so");
|
||||
#else
|
||||
_glfw.x11.xcursor.handle = _glfw_dlopen("libXcursor.so.1");
|
||||
#endif
|
||||
if (_glfw.x11.xcursor.handle)
|
||||
{
|
||||
_glfw.x11.xcursor.ImageCreate = (PFN_XcursorImageCreate)
|
||||
@@ -291,7 +303,11 @@ static GLFWbool initExtensions(void)
|
||||
_glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorImageLoadCursor");
|
||||
}
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
_glfw.x11.xinerama.handle = _glfw_dlopen("libXinerama-1.so");
|
||||
#else
|
||||
_glfw.x11.xinerama.handle = _glfw_dlopen("libXinerama.so.1");
|
||||
#endif
|
||||
if (_glfw.x11.xinerama.handle)
|
||||
{
|
||||
_glfw.x11.xinerama.IsActive = (PFN_XineramaIsActive)
|
||||
@@ -310,16 +326,11 @@ static GLFWbool initExtensions(void)
|
||||
}
|
||||
}
|
||||
|
||||
_glfw.x11.x11xcb.handle = _glfw_dlopen("libX11-xcb.so.1");
|
||||
if (!_glfw.x11.x11xcb.handle)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to load libX11-xcb");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
_glfw.x11.x11xcb.GetXCBConnection = (PFN_XGetXCBConnection)
|
||||
_glfw_dlsym(_glfw.x11.x11xcb.handle, "XGetXCBConnection");
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
_glfw.x11.xrender.handle = _glfw_dlopen("libXrender-1.so");
|
||||
#else
|
||||
_glfw.x11.xrender.handle = _glfw_dlopen("libXrender.so.1");
|
||||
#endif
|
||||
if (_glfw.x11.xrender.handle)
|
||||
{
|
||||
_glfw.x11.xrender.QueryExtension = (PFN_XRenderQueryExtension)
|
||||
@@ -659,12 +670,6 @@ void _glfwPlatformTerminate(void)
|
||||
_glfw.x11.display = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.x11xcb.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.x11.x11xcb.handle);
|
||||
_glfw.x11.x11xcb.handle = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.xcursor.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.x11.xcursor.handle);
|
||||
@@ -683,6 +688,24 @@ void _glfwPlatformTerminate(void)
|
||||
_glfw.x11.xinerama.handle = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.xrender.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.x11.xrender.handle);
|
||||
_glfw.x11.xrender.handle = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.vidmode.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.x11.vidmode.handle);
|
||||
_glfw.x11.vidmode.handle = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.xi.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.x11.xi.handle);
|
||||
_glfw.x11.xi.handle = NULL;
|
||||
}
|
||||
|
||||
// NOTE: These need to be unloaded after XCloseDisplay, as they register
|
||||
// cleanup callbacks that get called by that function
|
||||
_glfwTerminateEGL();
|
||||
|
||||
11
glfw/x11_platform.h
vendored
11
glfw/x11_platform.h
vendored
@@ -35,6 +35,9 @@
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xcursor/Xcursor.h>
|
||||
|
||||
// The xcb library is needed to work with libxkb
|
||||
#include <X11/Xlib-xcb.h>
|
||||
|
||||
// The XRandR extension provides mode setting and gamma control
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
|
||||
@@ -47,6 +50,7 @@
|
||||
// The XInput extension provides raw mouse motion input
|
||||
#include <X11/extensions/XInput2.h>
|
||||
|
||||
// The libxkb library is used for improved keyboard support
|
||||
#include "xkb_glfw.h"
|
||||
|
||||
typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int);
|
||||
@@ -99,8 +103,6 @@ typedef XineramaScreenInfo* (* PFN_XineramaQueryScreens)(Display*,int*);
|
||||
#define XineramaQueryScreens _glfw.x11.xinerama.QueryScreens
|
||||
|
||||
typedef struct xcb_connection_t xcb_connection_t;
|
||||
typedef xcb_connection_t* (* PFN_XGetXCBConnection)(Display*);
|
||||
#define XGetXCBConnection _glfw.x11.x11xcb.GetXCBConnection
|
||||
|
||||
typedef Bool (* PFN_XF86VidModeQueryExtension)(Display*,int*,int*);
|
||||
typedef Bool (* PFN_XF86VidModeGetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
|
||||
@@ -341,11 +343,6 @@ typedef struct _GLFWlibraryX11
|
||||
PFN_XineramaQueryScreens QueryScreens;
|
||||
} xinerama;
|
||||
|
||||
struct {
|
||||
void* handle;
|
||||
PFN_XGetXCBConnection GetXCBConnection;
|
||||
} x11xcb;
|
||||
|
||||
struct {
|
||||
GLFWbool available;
|
||||
void* handle;
|
||||
|
||||
123
glfw/x11_window.c
vendored
123
glfw/x11_window.c
vendored
@@ -535,6 +535,61 @@ static void updateCursorImage(_GLFWwindow* window)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply disabled cursor mode to a focused window
|
||||
//
|
||||
static void disableCursor(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.x11.xi.available)
|
||||
{
|
||||
XIEventMask em;
|
||||
unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 };
|
||||
|
||||
em.deviceid = XIAllMasterDevices;
|
||||
em.mask_len = sizeof(mask);
|
||||
em.mask = mask;
|
||||
XISetMask(mask, XI_RawMotion);
|
||||
|
||||
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
|
||||
}
|
||||
|
||||
_glfw.x11.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.x11.restoreCursorPosX,
|
||||
&_glfw.x11.restoreCursorPosY);
|
||||
updateCursorImage(window);
|
||||
centerCursor(window);
|
||||
XGrabPointer(_glfw.x11.display, window->x11.handle, True,
|
||||
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
|
||||
GrabModeAsync, GrabModeAsync,
|
||||
window->x11.handle,
|
||||
_glfw.x11.hiddenCursorHandle,
|
||||
CurrentTime);
|
||||
}
|
||||
|
||||
// Exit disabled cursor mode for the specified window
|
||||
//
|
||||
static void enableCursor(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.x11.xi.available)
|
||||
{
|
||||
XIEventMask em;
|
||||
unsigned char mask[] = { 0 };
|
||||
|
||||
em.deviceid = XIAllMasterDevices;
|
||||
em.mask_len = sizeof(mask);
|
||||
em.mask = mask;
|
||||
|
||||
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
|
||||
}
|
||||
|
||||
_glfw.x11.disabledCursorWindow = NULL;
|
||||
XUngrabPointer(_glfw.x11.display, CurrentTime);
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.x11.restoreCursorPosX,
|
||||
_glfw.x11.restoreCursorPosY);
|
||||
updateCursorImage(window);
|
||||
}
|
||||
|
||||
// Create the X11 window (and its colormap)
|
||||
//
|
||||
static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
@@ -1306,7 +1361,7 @@ static void processEvent(XEvent *event)
|
||||
// HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise
|
||||
// ignore the defined cursor for hidden cursor mode
|
||||
if (window->cursorMode == GLFW_CURSOR_HIDDEN)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_HIDDEN);
|
||||
updateCursorImage(window);
|
||||
|
||||
_glfwInputCursorEnter(window, GLFW_TRUE);
|
||||
return;
|
||||
@@ -1599,7 +1654,7 @@ static void processEvent(XEvent *event)
|
||||
case FocusIn:
|
||||
{
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED);
|
||||
disableCursor(window);
|
||||
|
||||
if (event->xfocus.mode == NotifyGrab ||
|
||||
event->xfocus.mode == NotifyUngrab)
|
||||
@@ -1616,7 +1671,7 @@ static void processEvent(XEvent *event)
|
||||
case FocusOut:
|
||||
{
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_NORMAL);
|
||||
enableCursor(window);
|
||||
|
||||
if (event->xfocus.mode == NotifyGrab ||
|
||||
event->xfocus.mode == NotifyUngrab)
|
||||
@@ -2484,8 +2539,9 @@ void _glfwPlatformPollEvents(void)
|
||||
#if defined(__linux__)
|
||||
_glfwDetectJoystickConnectionLinux();
|
||||
#endif
|
||||
int count = XPending(_glfw.x11.display);
|
||||
while (count--)
|
||||
XPending(_glfw.x11.display);
|
||||
|
||||
while (XQLength(_glfw.x11.display))
|
||||
{
|
||||
XEvent event;
|
||||
XNextEvent(_glfw.x11.display, &event);
|
||||
@@ -2575,53 +2631,14 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
||||
{
|
||||
if (mode == GLFW_CURSOR_DISABLED)
|
||||
{
|
||||
if (_glfw.x11.xi.available)
|
||||
{
|
||||
XIEventMask em;
|
||||
unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 };
|
||||
|
||||
em.deviceid = XIAllMasterDevices;
|
||||
em.mask_len = sizeof(mask);
|
||||
em.mask = mask;
|
||||
XISetMask(mask, XI_RawMotion);
|
||||
|
||||
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
|
||||
}
|
||||
|
||||
_glfw.x11.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.x11.restoreCursorPosX,
|
||||
&_glfw.x11.restoreCursorPosY);
|
||||
centerCursor(window);
|
||||
XGrabPointer(_glfw.x11.display, window->x11.handle, True,
|
||||
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
|
||||
GrabModeAsync, GrabModeAsync,
|
||||
window->x11.handle,
|
||||
_glfw.x11.hiddenCursorHandle,
|
||||
CurrentTime);
|
||||
if (_glfwPlatformWindowFocused(window))
|
||||
disableCursor(window);
|
||||
}
|
||||
else if (_glfw.x11.disabledCursorWindow == window)
|
||||
{
|
||||
if (_glfw.x11.xi.available)
|
||||
{
|
||||
XIEventMask em;
|
||||
unsigned char mask[] = { 0 };
|
||||
enableCursor(window);
|
||||
else
|
||||
updateCursorImage(window);
|
||||
|
||||
em.deviceid = XIAllMasterDevices;
|
||||
em.mask_len = sizeof(mask);
|
||||
em.mask = mask;
|
||||
|
||||
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
|
||||
}
|
||||
|
||||
_glfw.x11.disabledCursorWindow = NULL;
|
||||
XUngrabPointer(_glfw.x11.display, CurrentTime);
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.x11.restoreCursorPosX,
|
||||
_glfw.x11.restoreCursorPosY);
|
||||
}
|
||||
|
||||
updateCursorImage(window);
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
@@ -2704,7 +2721,7 @@ void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||
if (!_glfw.vk.KHR_surface)
|
||||
return;
|
||||
|
||||
if (!_glfw.vk.KHR_xcb_surface || !_glfw.x11.x11xcb.handle)
|
||||
if (!_glfw.vk.KHR_xcb_surface)
|
||||
{
|
||||
if (!_glfw.vk.KHR_xlib_surface)
|
||||
return;
|
||||
@@ -2714,7 +2731,7 @@ void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||
|
||||
// NOTE: VK_KHR_xcb_surface is preferred due to some early ICDs exposing but
|
||||
// not correctly implementing VK_KHR_xlib_surface
|
||||
if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle)
|
||||
if (_glfw.vk.KHR_xcb_surface)
|
||||
extensions[1] = "VK_KHR_xcb_surface";
|
||||
else
|
||||
extensions[1] = "VK_KHR_xlib_surface";
|
||||
@@ -2727,7 +2744,7 @@ int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
|
||||
VisualID visualID = XVisualIDFromVisual(DefaultVisual(_glfw.x11.display,
|
||||
_glfw.x11.screen));
|
||||
|
||||
if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle)
|
||||
if (_glfw.vk.KHR_xcb_surface)
|
||||
{
|
||||
PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR
|
||||
vkGetPhysicalDeviceXcbPresentationSupportKHR =
|
||||
@@ -2778,7 +2795,7 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
const VkAllocationCallbacks* allocator,
|
||||
VkSurfaceKHR* surface)
|
||||
{
|
||||
if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle)
|
||||
if (_glfw.vk.KHR_xcb_surface)
|
||||
{
|
||||
VkResult err;
|
||||
VkXcbSurfaceCreateInfoKHR sci;
|
||||
|
||||
7
glfw/xkb_glfw.c
vendored
7
glfw/xkb_glfw.c
vendored
@@ -263,12 +263,14 @@ glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
|
||||
S(super, XKB_MOD_NAME_LOGO);
|
||||
S(capsLock, XKB_MOD_NAME_CAPS);
|
||||
S(numLock, XKB_MOD_NAME_NUM);
|
||||
#undef S
|
||||
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
|
||||
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
|
||||
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
|
||||
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
|
||||
}
|
||||
#undef S
|
||||
xkb->modifiers = 0;
|
||||
xkb->activeUnknownModifiers = 0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
@@ -290,6 +292,9 @@ glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_m
|
||||
if (!xkb->keymap) return;
|
||||
xkb->modifiers = 0;
|
||||
xkb_state_update_mask(xkb->state, depressed, latched, locked, base_group, latched_group, locked_group);
|
||||
// We have to update the groups in clean_state, as they change for
|
||||
// different keyboard layouts, see https://github.com/kovidgoyal/kitty/issues/488
|
||||
xkb_state_update_mask(xkb->clean_state, 0, 0, 0, base_group, latched_group, locked_group);
|
||||
#define S(attr, name) if (xkb_state_mod_index_is_active(xkb->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) xkb->modifiers |= GLFW_MOD_##name
|
||||
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
|
||||
#undef S
|
||||
|
||||
@@ -53,7 +53,6 @@ class HistoryCompleter:
|
||||
if os.path.exists(self.history_path):
|
||||
readline.read_history_file(self.history_path)
|
||||
readline.set_completer(self.complete)
|
||||
readline.parse_and_bind('tab: complete')
|
||||
return self
|
||||
|
||||
def __exit__(self, *a):
|
||||
@@ -87,6 +86,7 @@ def main(args):
|
||||
global readline
|
||||
import readline as rl
|
||||
readline = rl
|
||||
from kitty.shell import init_readline
|
||||
msg = 'Ask the user for input'
|
||||
try:
|
||||
args, items = parse_args(args[1:], option_text, '', msg, 'kitty ask')
|
||||
@@ -96,7 +96,7 @@ def main(args):
|
||||
input('Press enter to quit...')
|
||||
raise SystemExit(e.code)
|
||||
|
||||
readline.read_init_file()
|
||||
init_readline(readline)
|
||||
ans = {'items': items}
|
||||
|
||||
with alternate_screen(), HistoryCompleter(args.name):
|
||||
|
||||
105
kittens/diff/README.asciidoc
Normal file
105
kittens/diff/README.asciidoc
Normal file
@@ -0,0 +1,105 @@
|
||||
= kitty-diff - A side-by-side diff tool with syntax highlighting and images
|
||||
:toc:
|
||||
:toc-placement!:
|
||||
|
||||
|
||||
== Major Features
|
||||
|
||||
* Displays diffs side-by-side in the kitty terminal.
|
||||
|
||||
* Does syntax highlighting of displayed diffs
|
||||
|
||||
* Displays images as well as text diffs, even over SSH
|
||||
|
||||
* Does recursive directory diffing
|
||||
|
||||
|
||||
image::../../screenshots/diff.png?raw=true[Screenshot, showing a sample diff]
|
||||
|
||||
toc::[]
|
||||
|
||||
|
||||
== Installation
|
||||
|
||||
Simply install link:https://github.com/kovidgoyal/kitty[kitty]. You also need
|
||||
to have either the link:https://git-scm.com/[git] program or the `diff` program
|
||||
installed. Additionally, for syntax highlighting to work,
|
||||
link:http://pygments.org/[pygments] must be installed (note that pygments is
|
||||
included in the macOS kitty app).
|
||||
|
||||
|
||||
== Usage
|
||||
|
||||
In the kitty terminal, run:
|
||||
|
||||
....
|
||||
kitty +kitten diff file1 file2
|
||||
....
|
||||
|
||||
to see the diff between file1 and file2.
|
||||
|
||||
Create an alias in your shell's startup file to shorten the command, for example:
|
||||
|
||||
```sh
|
||||
alias d="kitty +kitten diff"
|
||||
```
|
||||
|
||||
Now all you need to do to diff two files is:
|
||||
|
||||
```
|
||||
d file1 file2
|
||||
```
|
||||
|
||||
You can also pass directories instead of files to see the recursive diff of the
|
||||
directory contents.
|
||||
|
||||
|
||||
== Keyboard controls
|
||||
|
||||
|===
|
||||
|Action |Shortcut
|
||||
|
||||
|Quit | `q, Ctrl+c`
|
||||
|Scroll line up | `k, up`
|
||||
|Scroll line down | `j, down`
|
||||
|Scroll page up | `PgUp`
|
||||
|Scroll page down | `PgDn`
|
||||
|Scroll to top | `Home`
|
||||
|Scroll to bottom | `End`
|
||||
|Scroll to next change | `n`
|
||||
|Scroll to previous change | `p`
|
||||
|
||||
|Increase lines of context | `+`
|
||||
|Decrease lines of context | `-`
|
||||
|All lines of context | `a`
|
||||
|Restore context to default| `=`
|
||||
|
||||
|===
|
||||
|
||||
[options="header"]
|
||||
|
||||
|
||||
== Integrating with git
|
||||
|
||||
Add the following to `~/.gitconfig`:
|
||||
|
||||
```gitconfig
|
||||
[diff]
|
||||
tool = kitty
|
||||
guitool = kitty.gui
|
||||
[difftool]
|
||||
prompt = false
|
||||
trustExitCode = true
|
||||
[difftool "kitty"]
|
||||
cmd = kitty +kitten diff $LOCAL $REMOTE
|
||||
[difftool "kitty.gui"]
|
||||
cmd = kitty kitty +kitten diff $LOCAL $REMOTE
|
||||
```
|
||||
|
||||
Now to use kitty-diff to view git diffs, you can simply do:
|
||||
|
||||
```
|
||||
git difftool --no-symlinks --dir-diff
|
||||
```
|
||||
|
||||
Once again, creating an alias for this command is useful.
|
||||
0
kittens/diff/__init__.py
Normal file
0
kittens/diff/__init__.py
Normal file
189
kittens/diff/collect.py
Normal file
189
kittens/diff/collect.py
Normal file
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from hashlib import md5
|
||||
from mimetypes import guess_type
|
||||
|
||||
path_name_map = {}
|
||||
|
||||
|
||||
class Segment:
|
||||
|
||||
__slots__ = ('start', 'end', 'start_code', 'end_code')
|
||||
|
||||
def __init__(self, start, start_code):
|
||||
self.start = start
|
||||
self.start_code = start_code
|
||||
|
||||
def __repr__(self):
|
||||
return 'Segment(start={!r}, start_code={!r}, end={!r}, end_code={!r})'.format(
|
||||
self.start, self.start_code, getattr(self, 'end', None), getattr(self, 'end_code', None)
|
||||
)
|
||||
|
||||
|
||||
class Collection:
|
||||
|
||||
def __init__(self):
|
||||
self.changes = {}
|
||||
self.renames = {}
|
||||
self.adds = set()
|
||||
self.removes = set()
|
||||
self.all_paths = []
|
||||
self.type_map = {}
|
||||
self.added_count = self.removed_count = 0
|
||||
|
||||
def add_change(self, left_path, right_path):
|
||||
self.changes[left_path] = right_path
|
||||
self.all_paths.append(left_path)
|
||||
self.type_map[left_path] = 'diff'
|
||||
|
||||
def add_rename(self, left_path, right_path):
|
||||
self.renames[left_path] = right_path
|
||||
self.all_paths.append(left_path)
|
||||
self.type_map[left_path] = 'rename'
|
||||
|
||||
def add_add(self, right_path):
|
||||
self.adds.add(right_path)
|
||||
self.all_paths.append(right_path)
|
||||
self.type_map[right_path] = 'add'
|
||||
if isinstance(data_for_path(right_path), str):
|
||||
self.added_count += len(lines_for_path(right_path))
|
||||
|
||||
def add_removal(self, left_path):
|
||||
self.removes.add(left_path)
|
||||
self.all_paths.append(left_path)
|
||||
self.type_map[left_path] = 'removal'
|
||||
if isinstance(data_for_path(left_path), str):
|
||||
self.removed_count += len(lines_for_path(left_path))
|
||||
|
||||
def finalize(self):
|
||||
self.all_paths.sort(key=path_name_map.get)
|
||||
|
||||
def __iter__(self):
|
||||
for path in self.all_paths:
|
||||
typ = self.type_map[path]
|
||||
if typ == 'diff':
|
||||
data = self.changes[path]
|
||||
elif typ == 'rename':
|
||||
data = self.renames[path]
|
||||
else:
|
||||
data = None
|
||||
yield path, typ, data
|
||||
|
||||
def __len__(self):
|
||||
return len(self.all_paths)
|
||||
|
||||
|
||||
def collect_files(collection, left, right):
|
||||
left_names, right_names = set(), set()
|
||||
left_path_map, right_path_map = {}, {}
|
||||
|
||||
def walk(base, names, pmap):
|
||||
for dirpath, dirnames, filenames in os.walk(base):
|
||||
for filename in filenames:
|
||||
path = os.path.abspath(os.path.join(dirpath, filename))
|
||||
path_name_map[path] = name = os.path.relpath(path, base)
|
||||
names.add(name)
|
||||
pmap[name] = path
|
||||
|
||||
walk(left, left_names, left_path_map), walk(right, right_names, right_path_map)
|
||||
common_names = left_names & right_names
|
||||
changed_names = {n for n in common_names if data_for_path(left_path_map[n]) != data_for_path(right_path_map[n])}
|
||||
for n in changed_names:
|
||||
collection.add_change(left_path_map[n], right_path_map[n])
|
||||
|
||||
removed = left_names - common_names
|
||||
added = right_names - common_names
|
||||
ahash = {a: hash_for_path(right_path_map[a]) for a in added}
|
||||
rhash = {r: hash_for_path(left_path_map[r]) for r in removed}
|
||||
for name, rh in rhash.items():
|
||||
for n, ah in ahash.items():
|
||||
if ah == rh and data_for_path(left_path_map[name]) == data_for_path(right_path_map[n]):
|
||||
collection.add_rename(left_path_map[name], right_path_map[n])
|
||||
added.discard(n)
|
||||
break
|
||||
else:
|
||||
collection.add_removal(left_path_map[name])
|
||||
|
||||
for name in added:
|
||||
collection.add_add(right_path_map[name])
|
||||
|
||||
|
||||
sanitize_pat = re.compile('[\x00-\x09\x0b-\x1f\x7f\x80-\x9f]')
|
||||
|
||||
|
||||
def sanitize_sub(m):
|
||||
return '<{:x}>'.format(ord(m.group()[0]))
|
||||
|
||||
|
||||
def sanitize(text):
|
||||
return sanitize_pat.sub(sanitize_sub, text)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def mime_type_for_path(path):
|
||||
return guess_type(path)[0] or 'application/octet-stream'
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def raw_data_for_path(path):
|
||||
with open(path, 'rb') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def is_image(path):
|
||||
return mime_type_for_path(path).startswith('image/')
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def data_for_path(path):
|
||||
ans = raw_data_for_path(path)
|
||||
if not is_image(path) and not os.path.samefile(path, os.devnull):
|
||||
try:
|
||||
ans = ans.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
return ans
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def lines_for_path(path):
|
||||
data = data_for_path(path).replace('\t', lines_for_path.replace_tab_by)
|
||||
return tuple(sanitize(data).splitlines())
|
||||
|
||||
|
||||
lines_for_path.replace_tab_by = ' ' * 4
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def hash_for_path(path):
|
||||
return md5(raw_data_for_path(path)).digest()
|
||||
|
||||
|
||||
def create_collection(left, right):
|
||||
collection = Collection()
|
||||
if os.path.isdir(left):
|
||||
collect_files(collection, left, right)
|
||||
else:
|
||||
pl, pr = os.path.abspath(left), os.path.abspath(right)
|
||||
path_name_map[pl] = left
|
||||
path_name_map[pr] = right
|
||||
collection.add_change(pl, pr)
|
||||
collection.finalize()
|
||||
return collection
|
||||
|
||||
|
||||
highlight_data = {}
|
||||
|
||||
|
||||
def set_highlight_data(data):
|
||||
global highlight_data
|
||||
highlight_data = data
|
||||
|
||||
|
||||
def highlights_for_path(path):
|
||||
return highlight_data.get(path, [])
|
||||
111
kittens/diff/config.py
Normal file
111
kittens/diff/config.py
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
|
||||
from kitty.config_utils import (
|
||||
init_config, load_config as _load_config, merge_dicts, parse_config_base,
|
||||
python_string, resolve_config, to_color
|
||||
)
|
||||
from kitty.constants import config_dir
|
||||
from kitty.rgb import color_as_sgr
|
||||
|
||||
defaults = None
|
||||
default_config_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), 'diff.conf'
|
||||
)
|
||||
|
||||
formats = {
|
||||
'title': '',
|
||||
'margin': '',
|
||||
'text': '',
|
||||
}
|
||||
|
||||
|
||||
def set_formats(opts):
|
||||
formats['text'] = '48' + color_as_sgr(opts.background)
|
||||
formats['title'] = '38' + color_as_sgr(opts.title_fg) + ';48' + color_as_sgr(opts.title_bg) + ';1'
|
||||
formats['margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.margin_bg)
|
||||
formats['added_margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.added_margin_bg)
|
||||
formats['removed_margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.removed_margin_bg)
|
||||
formats['added'] = '48' + color_as_sgr(opts.added_bg)
|
||||
formats['removed'] = '48' + color_as_sgr(opts.removed_bg)
|
||||
formats['filler'] = '48' + color_as_sgr(opts.filler_bg)
|
||||
formats['hunk_margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.hunk_margin_bg)
|
||||
formats['hunk'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.hunk_bg)
|
||||
formats['removed_highlight'] = '48' + color_as_sgr(opts.highlight_removed_bg)
|
||||
formats['added_highlight'] = '48' + color_as_sgr(opts.highlight_added_bg)
|
||||
|
||||
|
||||
def syntax_aliases(raw):
|
||||
ans = {}
|
||||
for x in raw.split():
|
||||
a, b = x.partition(':')[::2]
|
||||
if a and b:
|
||||
ans[a.lower()] = b
|
||||
return ans
|
||||
|
||||
|
||||
type_map = {
|
||||
'syntax_aliases': syntax_aliases,
|
||||
'num_context_lines': int,
|
||||
'replace_tab_by': python_string,
|
||||
}
|
||||
|
||||
for name in (
|
||||
'foreground background title_fg title_bg margin_bg margin_fg removed_bg removed_margin_bg added_bg added_margin_bg filler_bg hunk_bg hunk_margin_bg'
|
||||
' highlight_removed_bg highlight_added_bg'
|
||||
).split():
|
||||
type_map[name] = to_color
|
||||
|
||||
|
||||
def special_handling(*a):
|
||||
pass
|
||||
|
||||
|
||||
def parse_config(lines, check_keys=True):
|
||||
ans = {}
|
||||
parse_config_base(
|
||||
lines,
|
||||
defaults,
|
||||
type_map,
|
||||
special_handling,
|
||||
ans,
|
||||
check_keys=check_keys
|
||||
)
|
||||
return ans
|
||||
|
||||
|
||||
def merge_configs(defaults, vals):
|
||||
ans = {}
|
||||
for k, v in defaults.items():
|
||||
if isinstance(v, dict):
|
||||
newvals = vals.get(k, {})
|
||||
ans[k] = merge_dicts(v, newvals)
|
||||
else:
|
||||
ans[k] = vals.get(k, v)
|
||||
return ans
|
||||
|
||||
|
||||
def parse_defaults(lines, check_keys=False):
|
||||
return parse_config(lines, check_keys)
|
||||
|
||||
|
||||
Options, defaults = init_config(default_config_path, parse_defaults)
|
||||
|
||||
|
||||
def load_config(*paths, overrides=None):
|
||||
return _load_config(Options, defaults, parse_config, merge_configs, *paths, overrides=overrides)
|
||||
|
||||
|
||||
SYSTEM_CONF = '/etc/xdg/kitty/diff.conf'
|
||||
defconf = os.path.join(config_dir, 'diff.conf')
|
||||
|
||||
|
||||
def init_config(args):
|
||||
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
|
||||
overrides = (a.replace('=', ' ', 1) for a in args.override or ())
|
||||
opts = load_config(*config, overrides=overrides)
|
||||
set_formats(opts)
|
||||
return opts
|
||||
38
kittens/diff/diff.conf
Normal file
38
kittens/diff/diff.conf
Normal file
@@ -0,0 +1,38 @@
|
||||
# vim:fileencoding=utf-8:ft=conf
|
||||
|
||||
# File extension aliases for syntax highlight
|
||||
# For example, to syntax highlight file.xyz as
|
||||
# file.abc use a setting of xyz:abc
|
||||
syntax_aliases pyj:py recipe:py
|
||||
|
||||
# The pygments color scheme to use. See https://help.farbox.com/pygments.html
|
||||
# for a list of schemes.
|
||||
pygments_style default
|
||||
|
||||
# The number of lines of context to show around each change.
|
||||
num_context_lines 3
|
||||
|
||||
# The diff command to use. Must contain the placeholder _CONTEXT_
|
||||
# which will be replaced by the number of lines of context. The default
|
||||
# is to search the system for either git or diff and use that, if found.
|
||||
diff_cmd auto
|
||||
|
||||
# The string to replace tabs with. Default is to use four spaces.
|
||||
replace_tab_by \x20\x20\x20\x20
|
||||
|
||||
# Colors
|
||||
foreground black
|
||||
background white
|
||||
title_fg black
|
||||
title_bg white
|
||||
margin_bg #fafbfc
|
||||
margin_fg #aaaaaa
|
||||
removed_bg #ffeef0
|
||||
highlight_removed_bg #fdb8c0
|
||||
removed_margin_bg #ffdce0
|
||||
added_bg #e6ffed
|
||||
highlight_added_bg #acf2bd
|
||||
added_margin_bg #cdffd8
|
||||
filler_bg #fafbfc
|
||||
hunk_margin_bg #dbedff
|
||||
hunk_bg #f1f8ff
|
||||
162
kittens/diff/highlight.py
Normal file
162
kittens/diff/highlight.py
Normal file
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import concurrent
|
||||
import os
|
||||
import re
|
||||
|
||||
from pygments import highlight
|
||||
from pygments.formatter import Formatter
|
||||
from pygments.lexers import get_lexer_for_filename
|
||||
from pygments.util import ClassNotFound
|
||||
|
||||
from kitty.rgb import color_as_sgr, parse_sharp
|
||||
|
||||
from .collect import Segment, data_for_path, lines_for_path
|
||||
|
||||
|
||||
class StyleNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DiffFormatter(Formatter):
|
||||
|
||||
def __init__(self, style='default'):
|
||||
try:
|
||||
Formatter.__init__(self, style=style)
|
||||
initialized = True
|
||||
except ClassNotFound:
|
||||
initialized = False
|
||||
if not initialized:
|
||||
raise StyleNotFound('pygments style "{}" not found'.format(style))
|
||||
|
||||
self.styles = {}
|
||||
for token, style in self.style:
|
||||
start = []
|
||||
end = []
|
||||
# a style item is a tuple in the following form:
|
||||
# colors are readily specified in hex: 'RRGGBB'
|
||||
if style['color']:
|
||||
start.append('38' + color_as_sgr(parse_sharp(style['color'])))
|
||||
end.append('39')
|
||||
if style['bold']:
|
||||
start.append('1')
|
||||
end.append('22')
|
||||
if style['italic']:
|
||||
start.append('3')
|
||||
end.append('23')
|
||||
if style['underline']:
|
||||
start.append('4')
|
||||
end.append('24')
|
||||
if start:
|
||||
start = '\033[{}m'.format(';'.join(start))
|
||||
end = '\033[{}m'.format(';'.join(end))
|
||||
self.styles[token] = start or '', end or ''
|
||||
|
||||
def format(self, tokensource, outfile):
|
||||
for ttype, value in tokensource:
|
||||
not_found = True
|
||||
if value != '\n':
|
||||
while ttype and not_found:
|
||||
tok = self.styles.get(ttype)
|
||||
if tok is None:
|
||||
ttype = ttype[:-1]
|
||||
else:
|
||||
on, off = tok
|
||||
lines = value.split('\n')
|
||||
for line in lines:
|
||||
if line:
|
||||
outfile.write(on + line + off)
|
||||
if line is not lines[-1]:
|
||||
outfile.write('\n')
|
||||
not_found = False
|
||||
|
||||
if not_found:
|
||||
outfile.write(value)
|
||||
|
||||
|
||||
formatter = None
|
||||
|
||||
|
||||
def initialize_highlighter(style='default'):
|
||||
global formatter
|
||||
formatter = DiffFormatter(style)
|
||||
|
||||
|
||||
def highlight_data(code, filename, aliases=None):
|
||||
if aliases:
|
||||
base, ext = os.path.splitext(filename)
|
||||
alias = aliases.get(ext[1:])
|
||||
if alias is not None:
|
||||
filename = base + '.' + alias
|
||||
try:
|
||||
lexer = get_lexer_for_filename(filename, stripnl=False)
|
||||
except ClassNotFound:
|
||||
pass
|
||||
else:
|
||||
return highlight(code, lexer, formatter)
|
||||
|
||||
|
||||
split_pat = re.compile(r'(\033\[.*?m)')
|
||||
|
||||
|
||||
def highlight_line(line):
|
||||
ans = []
|
||||
current = None
|
||||
pos = 0
|
||||
for x in split_pat.split(line):
|
||||
if x.startswith('\033'):
|
||||
if current is None:
|
||||
current = Segment(pos, x)
|
||||
else:
|
||||
current.end = pos
|
||||
current.end_code = x
|
||||
ans.append(current)
|
||||
current = None
|
||||
else:
|
||||
pos += len(x)
|
||||
return ans
|
||||
|
||||
|
||||
def highlight_for_diff(path, aliases):
|
||||
ans = []
|
||||
lines = lines_for_path(path)
|
||||
hd = highlight_data('\n'.join(lines), path, aliases)
|
||||
if hd is not None:
|
||||
for line in hd.splitlines():
|
||||
ans.append(highlight_line(line))
|
||||
return ans
|
||||
|
||||
|
||||
def highlight_collection(collection, aliases=None):
|
||||
jobs = {}
|
||||
ans = {}
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=os.cpu_count()) as executor:
|
||||
for path, item_type, other_path in collection:
|
||||
if item_type != 'rename':
|
||||
for p in (path, other_path):
|
||||
if p:
|
||||
is_binary = isinstance(data_for_path(p), bytes)
|
||||
if not is_binary:
|
||||
jobs[executor.submit(highlight_for_diff, p, aliases)] = p
|
||||
for future in concurrent.futures.as_completed(jobs):
|
||||
path = jobs[future]
|
||||
try:
|
||||
highlights = future.result()
|
||||
except Exception as e:
|
||||
return 'Running syntax highlighting for {} generated an exception: {}'.format(path, e)
|
||||
ans[path] = highlights
|
||||
return ans
|
||||
|
||||
|
||||
def main():
|
||||
from .config import defaults
|
||||
# kitty +runpy "from kittens.diff.highlight import main; main()" file
|
||||
import sys
|
||||
initialize_highlighter()
|
||||
with open(sys.argv[-1]) as f:
|
||||
highlighted = highlight_data(f.read(), f.name, defaults.syntax_aliases)
|
||||
if highlighted is None:
|
||||
raise SystemExit('Unknown filetype: {}'.format(sys.argv[-1]))
|
||||
print(highlighted)
|
||||
422
kittens/diff/main.py
Normal file
422
kittens/diff/main.py
Normal file
@@ -0,0 +1,422 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
from gettext import gettext as _
|
||||
|
||||
from kitty.cli import CONFIG_HELP, appname, parse_args
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from kitty.key_encoding import (
|
||||
DOWN, END, ESCAPE, HOME, PAGE_DOWN, PAGE_UP, RELEASE, UP
|
||||
)
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.images import ImageManager
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import styled
|
||||
from .collect import (
|
||||
create_collection, data_for_path, lines_for_path, set_highlight_data
|
||||
)
|
||||
from .config import init_config
|
||||
from .patch import Differ, set_diff_command
|
||||
from .render import ImageSupportWarning, LineRef, render_diff
|
||||
|
||||
try:
|
||||
from .highlight import initialize_highlighter, highlight_collection
|
||||
except ImportError:
|
||||
initialize_highlighter = None
|
||||
|
||||
|
||||
INITIALIZING, COLLECTED, DIFFED = range(3)
|
||||
|
||||
|
||||
def generate_diff(collection, context):
|
||||
d = Differ()
|
||||
|
||||
for path, item_type, changed_path in collection:
|
||||
if item_type == 'diff':
|
||||
is_binary = isinstance(data_for_path(path), bytes)
|
||||
if not is_binary:
|
||||
d.add_diff(path, changed_path)
|
||||
|
||||
return d(context)
|
||||
|
||||
|
||||
class DiffHandler(Handler):
|
||||
|
||||
image_manager_class = ImageManager
|
||||
|
||||
def __init__(self, args, opts, left, right):
|
||||
self.state = INITIALIZING
|
||||
self.opts = opts
|
||||
self.left, self.right = left, right
|
||||
self.report_traceback_on_exit = None
|
||||
self.args = args
|
||||
self.scroll_pos = self.max_scroll_pos = 0
|
||||
self.current_context_count = self.original_context_count = self.args.context
|
||||
if self.current_context_count < 0:
|
||||
self.current_context_count = self.original_context_count = self.opts.num_context_lines
|
||||
self.highlighting_done = False
|
||||
self.restore_position = None
|
||||
|
||||
def create_collection(self):
|
||||
self.start_job('collect', create_collection, self.left, self.right)
|
||||
|
||||
def generate_diff(self):
|
||||
self.start_job('diff', generate_diff, self.collection, self.current_context_count)
|
||||
|
||||
def calculate_statistics(self):
|
||||
self.added_count = self.collection.added_count
|
||||
self.removed_count = self.collection.removed_count
|
||||
for patch in self.diff_map.values():
|
||||
self.added_count += patch.added_count
|
||||
self.removed_count += patch.removed_count
|
||||
|
||||
def render_diff(self):
|
||||
self.diff_lines = tuple(render_diff(self.collection, self.diff_map, self.args, self.screen_size.cols, self.image_manager))
|
||||
self.ref_path_map = defaultdict(list)
|
||||
for i, l in enumerate(self.diff_lines):
|
||||
self.ref_path_map[l.ref.path].append((i, l.ref))
|
||||
self.max_scroll_pos = len(self.diff_lines) - self.num_lines
|
||||
|
||||
@property
|
||||
def current_position(self):
|
||||
return self.diff_lines[min(len(self.diff_lines) - 1, self.scroll_pos)].ref
|
||||
|
||||
@current_position.setter
|
||||
def current_position(self, ref):
|
||||
num = None
|
||||
if isinstance(ref.extra, LineRef):
|
||||
sln = ref.extra.src_line_number
|
||||
for i, q in self.ref_path_map[ref.path]:
|
||||
if isinstance(q.extra, LineRef):
|
||||
if q.extra.src_line_number >= sln:
|
||||
if q.extra.src_line_number == sln:
|
||||
num = i
|
||||
break
|
||||
num = i
|
||||
if num is None:
|
||||
for i, q in self.ref_path_map[ref.path]:
|
||||
num = i
|
||||
break
|
||||
|
||||
if num is not None:
|
||||
self.scroll_pos = min(num, self.max_scroll_pos)
|
||||
|
||||
@property
|
||||
def num_lines(self):
|
||||
return self.screen_size.rows - 1
|
||||
|
||||
def scroll_to_next_change(self, backwards=False):
|
||||
if backwards:
|
||||
r = range(self.scroll_pos - 1, -1, -1)
|
||||
else:
|
||||
r = range(self.scroll_pos + 1, len(self.diff_lines))
|
||||
for i in r:
|
||||
line = self.diff_lines[i]
|
||||
if line.is_change_start:
|
||||
self.scroll_lines(i - self.scroll_pos)
|
||||
return
|
||||
self.cmd.bell()
|
||||
|
||||
def set_scrolling_region(self):
|
||||
self.cmd.set_scrolling_region(self.screen_size, 0, self.num_lines - 2)
|
||||
|
||||
def scroll_lines(self, amt=1):
|
||||
new_pos = max(0, min(self.scroll_pos + amt, self.max_scroll_pos))
|
||||
if new_pos == self.scroll_pos:
|
||||
self.cmd.bell()
|
||||
return
|
||||
if abs(new_pos - self.scroll_pos) >= self.num_lines - 1:
|
||||
self.scroll_pos = new_pos
|
||||
self.draw_screen()
|
||||
return
|
||||
self.enforce_cursor_state()
|
||||
self.cmd.scroll_screen(amt)
|
||||
self.scroll_pos = new_pos
|
||||
if amt < 0:
|
||||
self.cmd.set_cursor_position(0, 0)
|
||||
self.draw_lines(-amt)
|
||||
else:
|
||||
self.cmd.set_cursor_position(0, self.num_lines - amt)
|
||||
self.draw_lines(amt, self.num_lines - amt)
|
||||
self.draw_status_line()
|
||||
|
||||
def init_terminal_state(self):
|
||||
self.cmd.set_line_wrapping(False)
|
||||
self.cmd.set_window_title(main.title)
|
||||
self.cmd.set_default_colors(self.opts.foreground, self.opts.background)
|
||||
|
||||
def initialize(self):
|
||||
self.init_terminal_state()
|
||||
self.set_scrolling_region()
|
||||
self.draw_screen()
|
||||
self.create_collection()
|
||||
|
||||
def enforce_cursor_state(self):
|
||||
self.cmd.set_cursor_visible(self.state > DIFFED)
|
||||
|
||||
def finalize(self):
|
||||
self.cmd.set_cursor_visible(True)
|
||||
self.cmd.set_default_colors()
|
||||
self.cmd.set_scrolling_region()
|
||||
|
||||
def draw_lines(self, num, offset=0):
|
||||
offset += self.scroll_pos
|
||||
image_involved = False
|
||||
limit = len(self.diff_lines)
|
||||
for i in range(num):
|
||||
lpos = offset + i
|
||||
if lpos >= limit:
|
||||
text = ''
|
||||
else:
|
||||
line = self.diff_lines[lpos]
|
||||
text = line.text
|
||||
if line.image_data is not None:
|
||||
image_involved = True
|
||||
self.write('\r\x1b[K' + text + '\x1b[0m')
|
||||
if i < num - 1:
|
||||
self.write('\n')
|
||||
if image_involved:
|
||||
self.place_images()
|
||||
|
||||
def place_images(self):
|
||||
self.cmd.clear_images_on_screen()
|
||||
offset = self.scroll_pos
|
||||
limit = len(self.diff_lines)
|
||||
in_image = False
|
||||
for row in range(self.num_lines):
|
||||
lpos = offset + row
|
||||
if lpos >= limit:
|
||||
break
|
||||
line = self.diff_lines[lpos]
|
||||
if in_image:
|
||||
if line.image_data is None:
|
||||
in_image = False
|
||||
continue
|
||||
if line.image_data is not None:
|
||||
left_placement, right_placement = line.image_data
|
||||
if left_placement is not None:
|
||||
self.place_image(row, left_placement, True)
|
||||
in_image = True
|
||||
if right_placement is not None:
|
||||
self.place_image(row, right_placement, False)
|
||||
in_image = True
|
||||
|
||||
def place_image(self, row, placement, is_left):
|
||||
xpos = (0 if is_left else (self.screen_size.cols // 2)) + placement.image.margin_size
|
||||
image_height_in_rows = placement.image.rows
|
||||
topmost_visible_row = placement.row
|
||||
num_visible_rows = image_height_in_rows - topmost_visible_row
|
||||
visible_frac = min(num_visible_rows / image_height_in_rows, 1)
|
||||
if visible_frac > 0:
|
||||
height = int(visible_frac * placement.image.height)
|
||||
top = placement.image.height - height
|
||||
self.image_manager.show_image(placement.image.image_id, xpos, row, src_rect=(
|
||||
0, top, placement.image.width, height))
|
||||
|
||||
def draw_screen(self):
|
||||
self.enforce_cursor_state()
|
||||
if self.state < DIFFED:
|
||||
self.cmd.clear_screen()
|
||||
self.write(_('Calculating diff, please wait...'))
|
||||
return
|
||||
self.cmd.clear_images_on_screen()
|
||||
self.cmd.set_cursor_position(0, 0)
|
||||
self.draw_lines(self.num_lines)
|
||||
self.draw_status_line()
|
||||
|
||||
def draw_status_line(self):
|
||||
if self.state < DIFFED:
|
||||
return
|
||||
self.cmd.set_cursor_position(0, self.num_lines)
|
||||
self.cmd.clear_to_eol()
|
||||
scroll_frac = styled('{:.0%}'.format(self.scroll_pos / self.max_scroll_pos), fg=self.opts.margin_fg)
|
||||
counts = '{}{}{}'.format(
|
||||
styled(str(self.added_count), fg=self.opts.highlight_added_bg),
|
||||
styled(',', fg=self.opts.margin_fg),
|
||||
styled(str(self.removed_count), fg=self.opts.highlight_removed_bg)
|
||||
)
|
||||
suffix = counts + ' ' + scroll_frac
|
||||
prefix = styled(':', fg=self.opts.margin_fg)
|
||||
filler = self.screen_size.cols - wcswidth(prefix) - wcswidth(suffix)
|
||||
text = '{}{}{}'.format(prefix, ' ' * filler, suffix)
|
||||
self.write(text)
|
||||
|
||||
def change_context_count(self, new_ctx):
|
||||
new_ctx = max(0, new_ctx)
|
||||
if new_ctx != self.current_context_count:
|
||||
self.current_context_count = new_ctx
|
||||
self.state = COLLECTED
|
||||
self.generate_diff()
|
||||
self.restore_position = self.current_position
|
||||
self.draw_screen()
|
||||
|
||||
def on_text(self, text, in_bracketed_paste=False):
|
||||
if text == 'q':
|
||||
if self.state <= DIFFED:
|
||||
self.quit_loop(0)
|
||||
return
|
||||
if self.state is DIFFED:
|
||||
if text in 'jk':
|
||||
self.scroll_lines(1 if text == 'j' else -1)
|
||||
return
|
||||
if text in 'a+-=':
|
||||
new_ctx = self.current_context_count
|
||||
if text == 'a':
|
||||
new_ctx = 100000
|
||||
elif text == '=':
|
||||
new_ctx = self.original_context_count
|
||||
else:
|
||||
new_ctx += (-1 if text == '-' else 1) * 5
|
||||
self.change_context_count(new_ctx)
|
||||
if text in 'np':
|
||||
self.scroll_to_next_change(backwards=text == 'p')
|
||||
return
|
||||
|
||||
def on_key(self, key_event):
|
||||
if key_event.type is RELEASE:
|
||||
return
|
||||
if key_event.key is ESCAPE:
|
||||
if self.state <= DIFFED:
|
||||
self.quit_loop(0)
|
||||
return
|
||||
if self.state is DIFFED:
|
||||
if key_event.key is UP or key_event.key is DOWN:
|
||||
self.scroll_lines(1 if key_event.key is DOWN else -1)
|
||||
return
|
||||
if key_event.key is PAGE_UP or key_event.key is PAGE_DOWN:
|
||||
amt = self.num_lines * (1 if key_event.key is PAGE_DOWN else -1)
|
||||
self.scroll_lines(amt)
|
||||
return
|
||||
if key_event.key is HOME or key_event.key is END:
|
||||
amt = len(self.diff_lines) * (1 if key_event.key is END else -1)
|
||||
self.scroll_lines(amt)
|
||||
return
|
||||
|
||||
def on_resize(self, screen_size):
|
||||
self.screen_size = screen_size
|
||||
self.set_scrolling_region()
|
||||
if self.state > COLLECTED:
|
||||
self.image_manager.delete_all_sent_images()
|
||||
self.render_diff()
|
||||
self.draw_screen()
|
||||
|
||||
def on_job_done(self, job_id, job_result):
|
||||
if 'tb' in job_result:
|
||||
self.report_traceback_on_exit = job_result['tb']
|
||||
self.quit_loop(1)
|
||||
return
|
||||
if job_id == 'collect':
|
||||
self.collection = job_result['result']
|
||||
self.state = COLLECTED
|
||||
self.generate_diff()
|
||||
elif job_id == 'diff':
|
||||
diff_map = job_result['result']
|
||||
if isinstance(diff_map, str):
|
||||
self.report_traceback_on_exit = diff_map
|
||||
self.quit_loop(1)
|
||||
return
|
||||
self.state = DIFFED
|
||||
self.diff_map = diff_map
|
||||
self.calculate_statistics()
|
||||
self.render_diff()
|
||||
self.scroll_pos = 0
|
||||
if self.restore_position is not None:
|
||||
self.current_position = self.restore_position
|
||||
self.restore_position = None
|
||||
self.draw_screen()
|
||||
if initialize_highlighter is not None and not self.highlighting_done:
|
||||
from .highlight import StyleNotFound
|
||||
self.highlighting_done = True
|
||||
try:
|
||||
initialize_highlighter(self.opts.pygments_style)
|
||||
except StyleNotFound as e:
|
||||
self.report_traceback_on_exit = str(e)
|
||||
self.quit_loop(1)
|
||||
return
|
||||
self.start_job('highlight', highlight_collection, self.collection, self.opts.syntax_aliases)
|
||||
elif job_id == 'highlight':
|
||||
hdata = job_result['result']
|
||||
if isinstance(hdata, str):
|
||||
self.report_traceback_on_exit = diff_map
|
||||
self.quit_loop(1)
|
||||
return
|
||||
set_highlight_data(hdata)
|
||||
self.render_diff()
|
||||
self.draw_screen()
|
||||
|
||||
def on_interrupt(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_eot(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
|
||||
OPTIONS = partial('''\
|
||||
--context
|
||||
type=int
|
||||
default=-1
|
||||
Number of lines of context to show between changes. Negative values
|
||||
use the number set in diff.conf
|
||||
|
||||
|
||||
--config
|
||||
type=list
|
||||
{config_help}
|
||||
|
||||
|
||||
--override -o
|
||||
type=list
|
||||
Override individual configuration options, can be specified multiple times.
|
||||
Syntax: |_ name=value|. For example: |_ -o background=gray|
|
||||
|
||||
'''.format, config_help=CONFIG_HELP.format(conf_name='diff', appname=appname))
|
||||
|
||||
|
||||
def showwarning(message, category, filename, lineno, file=None, line=None):
|
||||
if category is ImageSupportWarning:
|
||||
showwarning.warnings.append(message)
|
||||
|
||||
|
||||
showwarning.warnings = []
|
||||
|
||||
|
||||
def main(args):
|
||||
warnings.showwarning = showwarning
|
||||
msg = 'Show a side-by-side diff of the specified files/directories'
|
||||
args, items = parse_args(args[1:], OPTIONS, 'file_or_directory file_or_directory', msg, 'kitty +kitten diff')
|
||||
if len(items) != 2:
|
||||
raise SystemExit('You must specify exactly two files/directories to compare')
|
||||
left, right = items
|
||||
main.title = _('{} vs. {}').format(left, right)
|
||||
if os.path.isdir(left) != os.path.isdir(right):
|
||||
raise SystemExit('The items to be diffed should both be either directories or files. Comparing a directory to a file is not valid.')
|
||||
opts = init_config(args)
|
||||
set_diff_command(opts.diff_cmd)
|
||||
lines_for_path.replace_tab_by = opts.replace_tab_by
|
||||
|
||||
loop = Loop()
|
||||
handler = DiffHandler(args, opts, left, right)
|
||||
loop.loop(handler)
|
||||
for message in showwarning.warnings:
|
||||
from kitty.utils import safe_print
|
||||
safe_print(message, file=sys.stderr)
|
||||
if loop.return_code != 0:
|
||||
if handler.report_traceback_on_exit:
|
||||
print(handler.report_traceback_on_exit, file=sys.stderr)
|
||||
input('Press Enter to quit.')
|
||||
raise SystemExit(loop.return_code)
|
||||
|
||||
|
||||
def handle_result(args, current_char, target_window_id, boss):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
237
kittens/diff/patch.py
Normal file
237
kittens/diff/patch.py
Normal file
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import concurrent.futures
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from .collect import lines_for_path
|
||||
from .diff_speedup import changed_center
|
||||
|
||||
left_lines = right_lines = None
|
||||
GIT_DIFF = 'git diff --no-color --no-ext-diff --exit-code -U_CONTEXT_ --no-index --'
|
||||
DIFF_DIFF = 'diff -p -U _CONTEXT_ --'
|
||||
|
||||
|
||||
def find_differ():
|
||||
if shutil.which('git'):
|
||||
return GIT_DIFF
|
||||
if shutil.which('diff'):
|
||||
return DIFF_DIFF
|
||||
|
||||
|
||||
def set_diff_command(opt):
|
||||
if opt == 'auto':
|
||||
cmd = find_differ()
|
||||
if cmd is None:
|
||||
raise SystemExit('Failed to find either the git or diff programs on your system')
|
||||
else:
|
||||
cmd = opt
|
||||
set_diff_command.cmd = cmd
|
||||
|
||||
|
||||
def run_diff(file1, file2, context=3):
|
||||
# returns: ok, is_different, patch
|
||||
cmd = shlex.split(set_diff_command.cmd.replace('_CONTEXT_', str(context)))
|
||||
p = subprocess.Popen(
|
||||
cmd + [file1, file2],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
|
||||
stdout, stderr = p.communicate()
|
||||
returncode = p.wait()
|
||||
if returncode in (0, 1):
|
||||
return True, returncode == 1, stdout.decode('utf-8')
|
||||
return False, returncode, stderr.decode('utf-8')
|
||||
|
||||
|
||||
class Chunk:
|
||||
|
||||
__slots__ = ('is_context', 'left_start', 'right_start', 'left_count', 'right_count', 'centers')
|
||||
|
||||
def __init__(self, left_start, right_start, is_context=False):
|
||||
self.is_context = is_context
|
||||
self.left_start = left_start
|
||||
self.right_start = right_start
|
||||
self.left_count = self.right_count = 0
|
||||
self.centers = None
|
||||
|
||||
def add_line(self):
|
||||
self.right_count += 1
|
||||
|
||||
def remove_line(self):
|
||||
self.left_count += 1
|
||||
|
||||
def context_line(self):
|
||||
self.left_count += 1
|
||||
self.right_count += 1
|
||||
|
||||
def finalize(self):
|
||||
if not self.is_context and self.left_count == self.right_count:
|
||||
self.centers = tuple(
|
||||
changed_center(left_lines[self.left_start + i], right_lines[self.right_start + i])
|
||||
for i in range(self.left_count)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Chunk(is_context={}, left_start={}, left_count={}, right_start={}, right_count={})'.format(
|
||||
self.is_context, self.left_start, self.left_count, self.right_start, self.right_count)
|
||||
|
||||
|
||||
class Hunk:
|
||||
|
||||
def __init__(self, title, left, right):
|
||||
self.left_start, self.left_count = left
|
||||
self.right_start, self.right_count = right
|
||||
self.left_start -= 1 # 0-index
|
||||
self.right_start -= 1 # 0-index
|
||||
self.title = title
|
||||
self.added_count = self.removed_count = 0
|
||||
self.chunks = []
|
||||
self.current_chunk = None
|
||||
self.largest_line_number = max(self.left_start + self.left_count, self.right_start + self.right_count)
|
||||
|
||||
def new_chunk(self, is_context=False):
|
||||
if self.chunks:
|
||||
c = self.chunks[-1]
|
||||
left_start = c.left_start + c.left_count
|
||||
right_start = c.right_start + c.right_count
|
||||
else:
|
||||
left_start = self.left_start
|
||||
right_start = self.right_start
|
||||
return Chunk(left_start, right_start, is_context)
|
||||
|
||||
def ensure_diff_chunk(self):
|
||||
if self.current_chunk is None:
|
||||
self.current_chunk = self.new_chunk(is_context=False)
|
||||
elif self.current_chunk.is_context:
|
||||
self.chunks.append(self.current_chunk)
|
||||
self.current_chunk = self.new_chunk(is_context=False)
|
||||
|
||||
def ensure_context_chunk(self):
|
||||
if self.current_chunk is None:
|
||||
self.current_chunk = self.new_chunk(is_context=True)
|
||||
elif not self.current_chunk.is_context:
|
||||
self.chunks.append(self.current_chunk)
|
||||
self.current_chunk = self.new_chunk(is_context=True)
|
||||
|
||||
def add_line(self):
|
||||
self.ensure_diff_chunk()
|
||||
self.current_chunk.add_line()
|
||||
self.added_count += 1
|
||||
|
||||
def remove_line(self):
|
||||
self.ensure_diff_chunk()
|
||||
self.current_chunk.remove_line()
|
||||
self.removed_count += 1
|
||||
|
||||
def context_line(self):
|
||||
self.ensure_context_chunk()
|
||||
self.current_chunk.context_line()
|
||||
|
||||
def finalize(self):
|
||||
self.chunks.append(self.current_chunk)
|
||||
del self.current_chunk
|
||||
# Sanity check
|
||||
c = self.chunks[-1]
|
||||
if c.left_start + c.left_count != self.left_start + self.left_count:
|
||||
raise ValueError('Left side line mismatch {} != {}'.format(c.left_start + c.left_count, self.left_start + self.left_count))
|
||||
if c.right_start + c.right_count != self.right_start + self.right_count:
|
||||
raise ValueError('Left side line mismatch {} != {}'.format(c.right_start + c.right_count, self.right_start + self.right_count))
|
||||
for c in self.chunks:
|
||||
c.finalize()
|
||||
|
||||
|
||||
def parse_range(x):
|
||||
parts = x[1:].split(',', 1)
|
||||
start = abs(int(parts[0]))
|
||||
count = 1 if len(parts) < 2 else int(parts[1])
|
||||
return start, count
|
||||
|
||||
|
||||
def parse_hunk_header(line):
|
||||
parts = tuple(filter(None, line.split('@@', 2)))
|
||||
linespec = parts[0].strip()
|
||||
title = ''
|
||||
if len(parts) == 2:
|
||||
title = parts[1].strip()
|
||||
left, right = map(parse_range, linespec.split())
|
||||
return Hunk(title, left, right)
|
||||
|
||||
|
||||
class Patch:
|
||||
|
||||
def __init__(self, all_hunks):
|
||||
self.all_hunks = all_hunks
|
||||
self.largest_line_number = self.all_hunks[-1].largest_line_number if self.all_hunks else 0
|
||||
self.added_count = sum(h.added_count for h in all_hunks)
|
||||
self.removed_count = sum(h.removed_count for h in all_hunks)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.all_hunks)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.all_hunks)
|
||||
|
||||
|
||||
def parse_patch(raw):
|
||||
all_hunks = []
|
||||
current_hunk = None
|
||||
for line in raw.splitlines():
|
||||
if line.startswith('@@ '):
|
||||
current_hunk = parse_hunk_header(line)
|
||||
all_hunks.append(current_hunk)
|
||||
else:
|
||||
if current_hunk is None:
|
||||
continue
|
||||
q = line[0]
|
||||
if q == '+':
|
||||
current_hunk.add_line()
|
||||
elif q == '-':
|
||||
current_hunk.remove_line()
|
||||
elif q == '\\':
|
||||
continue
|
||||
else:
|
||||
current_hunk.context_line()
|
||||
for h in all_hunks:
|
||||
h.finalize()
|
||||
return Patch(all_hunks)
|
||||
|
||||
|
||||
class Differ:
|
||||
|
||||
def __init__(self):
|
||||
self.jmap = {}
|
||||
self.jobs = []
|
||||
|
||||
def add_diff(self, file1, file2):
|
||||
self.jmap[file1] = file2
|
||||
self.jobs.append(file1)
|
||||
|
||||
def __call__(self, context=3):
|
||||
global left_lines, right_lines
|
||||
ans = {}
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
|
||||
jobs = {executor.submit(run_diff, key, self.jmap[key], context): key for key in self.jobs}
|
||||
for future in concurrent.futures.as_completed(jobs):
|
||||
key = jobs[future]
|
||||
left_path, right_path = key, self.jmap[key]
|
||||
try:
|
||||
ok, returncode, output = future.result()
|
||||
except FileNotFoundError as err:
|
||||
return 'Could not find the {} executable. Is it in your PATH?'.format(err.filename)
|
||||
except Exception as e:
|
||||
return 'Running git diff for {} vs. {} generated an exception: {}'.format(left_path, right_path, e)
|
||||
if not ok:
|
||||
return output + '\nRunning git diff for {} vs. {} failed'.format(left_path, right_path)
|
||||
left_lines = lines_for_path(left_path)
|
||||
right_lines = lines_for_path(right_path)
|
||||
try:
|
||||
patch = parse_patch(output)
|
||||
except Exception:
|
||||
import traceback
|
||||
return traceback.format_exc() + '\nParsing diff for {} vs. {} failed'.format(left_path, right_path)
|
||||
else:
|
||||
ans[key] = patch
|
||||
return ans
|
||||
489
kittens/diff/render.py
Normal file
489
kittens/diff/render.py
Normal file
@@ -0,0 +1,489 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import warnings
|
||||
from gettext import gettext as _
|
||||
from itertools import repeat, zip_longest
|
||||
from math import ceil
|
||||
|
||||
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
||||
|
||||
from ..tui.images import can_display_images, screen_size
|
||||
from .collect import (
|
||||
Segment, data_for_path, highlights_for_path, is_image, lines_for_path,
|
||||
path_name_map, sanitize
|
||||
)
|
||||
from .config import formats
|
||||
from .diff_speedup import split_with_highlights as _split_with_highlights
|
||||
|
||||
|
||||
class ImageSupportWarning(Warning):
|
||||
pass
|
||||
|
||||
|
||||
def images_supported():
|
||||
ans = getattr(images_supported, 'ans', None)
|
||||
if ans is None:
|
||||
images_supported.ans = ans = can_display_images()
|
||||
if not ans:
|
||||
warnings.warn('ImageMagick not found images cannot be displayed', ImageSupportWarning)
|
||||
return ans
|
||||
|
||||
|
||||
class Ref:
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
raise AttributeError("can't set attribute")
|
||||
|
||||
def __repr__(self):
|
||||
return '{}({})'.format(self.__class__.__name__, ', '.join(
|
||||
'{}={}'.format(n, getattr(self, n)) for n in self.__slots__ if n != '_hash'))
|
||||
|
||||
|
||||
class LineRef(Ref):
|
||||
|
||||
__slots__ = ('src_line_number', 'wrapped_line_idx')
|
||||
|
||||
def __init__(self, sln, wli=0):
|
||||
object.__setattr__(self, 'src_line_number', sln)
|
||||
object.__setattr__(self, 'wrapped_line_idx', wli)
|
||||
|
||||
|
||||
class Reference(Ref):
|
||||
|
||||
__slots__ = ('path', 'extra')
|
||||
|
||||
def __init__(self, path, extra=None):
|
||||
object.__setattr__(self, 'path', path)
|
||||
object.__setattr__(self, 'extra', extra)
|
||||
|
||||
|
||||
class Line:
|
||||
|
||||
__slots__ = ('text', 'ref', 'is_change_start', 'image_data')
|
||||
|
||||
def __init__(self, text, ref, change_start=False, image_data=None):
|
||||
self.text = text
|
||||
self.ref = ref
|
||||
self.is_change_start = change_start
|
||||
self.image_data = image_data
|
||||
|
||||
|
||||
def yield_lines_from(iterator, reference, is_change_start=True):
|
||||
for text in iterator:
|
||||
yield Line(text, reference, is_change_start)
|
||||
is_change_start = False
|
||||
|
||||
|
||||
def human_readable(size, sep=' '):
|
||||
""" Convert a size in bytes into a human readable form """
|
||||
divisor, suffix = 1, "B"
|
||||
for i, candidate in enumerate(('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')):
|
||||
if size < (1 << ((i + 1) * 10)):
|
||||
divisor, suffix = (1 << (i * 10)), candidate
|
||||
break
|
||||
size = str(float(size)/divisor)
|
||||
if size.find(".") > -1:
|
||||
size = size[:size.find(".")+2]
|
||||
if size.endswith('.0'):
|
||||
size = size[:-2]
|
||||
return size + sep + suffix
|
||||
|
||||
|
||||
def fit_in(text, count):
|
||||
p = truncate_point_for_length(text, count)
|
||||
if p >= len(text):
|
||||
return text
|
||||
if count > 1:
|
||||
p = truncate_point_for_length(text, count - 1)
|
||||
return text[:p] + '…'
|
||||
|
||||
|
||||
def fill_in(text, sz):
|
||||
w = wcswidth(text)
|
||||
if w < sz:
|
||||
text += ' ' * (sz - w)
|
||||
return text
|
||||
|
||||
|
||||
def place_in(text, sz):
|
||||
return fill_in(fit_in(text, sz), sz)
|
||||
|
||||
|
||||
def format_func(which):
|
||||
def formatted(text):
|
||||
fmt = formats[which]
|
||||
return '\x1b[' + fmt + 'm' + text + '\x1b[0m'
|
||||
formatted.__name__ = which + '_format'
|
||||
return formatted
|
||||
|
||||
|
||||
text_format = format_func('text')
|
||||
title_format = format_func('title')
|
||||
margin_format = format_func('margin')
|
||||
added_format = format_func('added')
|
||||
removed_format = format_func('removed')
|
||||
removed_margin_format = format_func('removed_margin')
|
||||
added_margin_format = format_func('added_margin')
|
||||
filler_format = format_func('filler')
|
||||
hunk_margin_format = format_func('hunk_margin')
|
||||
hunk_format = format_func('hunk')
|
||||
highlight_map = {'remove': ('removed_highlight', 'removed'), 'add': ('added_highlight', 'added')}
|
||||
|
||||
|
||||
def highlight_boundaries(ltype):
|
||||
s, e = highlight_map[ltype]
|
||||
start = '\x1b[' + formats[s] + 'm'
|
||||
stop = '\x1b[' + formats[e] + 'm'
|
||||
return start, stop
|
||||
|
||||
|
||||
def title_lines(left_path, right_path, args, columns, margin_size):
|
||||
m = ' ' * margin_size
|
||||
left_name, right_name = map(path_name_map.get, (left_path, right_path))
|
||||
if right_name and right_name != left_name:
|
||||
n1 = fit_in(m + sanitize(left_name), columns // 2 - margin_size)
|
||||
n1 = place_in(n1, columns // 2)
|
||||
n2 = fit_in(m + sanitize(right_name), columns // 2 - margin_size)
|
||||
n2 = place_in(n2, columns // 2)
|
||||
name = n1 + n2
|
||||
else:
|
||||
name = place_in(m + sanitize(left_name), columns)
|
||||
yield title_format(place_in(name, columns))
|
||||
yield title_format('━' * columns)
|
||||
|
||||
|
||||
def binary_lines(path, other_path, columns, margin_size):
|
||||
template = _('Binary file: {}')
|
||||
available_cols = columns // 2 - margin_size
|
||||
|
||||
def fl(path, fmt):
|
||||
text = template.format(human_readable(len(data_for_path(path))))
|
||||
text = place_in(text, available_cols)
|
||||
return margin_format(' ' * margin_size) + fmt(text)
|
||||
|
||||
if path is None:
|
||||
filler = render_diff_line('', '', 'filler', margin_size, available_cols)
|
||||
yield filler + fl(other_path, added_format)
|
||||
elif other_path is None:
|
||||
filler = render_diff_line('', '', 'filler', margin_size, available_cols)
|
||||
yield fl(path, removed_format) + filler
|
||||
else:
|
||||
yield fl(path, removed_format) + fl(other_path, added_format)
|
||||
|
||||
|
||||
def split_to_size(line, width):
|
||||
if not line:
|
||||
yield line
|
||||
while line:
|
||||
p = truncate_point_for_length(line, width)
|
||||
yield line[:p]
|
||||
line = line[p:]
|
||||
|
||||
|
||||
def truncate_points(line, width):
|
||||
pos = 0
|
||||
sz = len(line)
|
||||
while True:
|
||||
pos = truncate_point_for_length(line, width, pos)
|
||||
if pos < sz:
|
||||
yield pos
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def split_with_highlights(line, width, highlights, bg_highlight=None):
|
||||
truncate_pts = list(truncate_points(line, width))
|
||||
return _split_with_highlights(line, truncate_pts, highlights, bg_highlight)
|
||||
|
||||
|
||||
margin_bg_map = {'filler': filler_format, 'remove': removed_margin_format, 'add': added_margin_format, 'context': margin_format}
|
||||
text_bg_map = {'filler': filler_format, 'remove': removed_format, 'add': added_format, 'context': text_format}
|
||||
|
||||
|
||||
class DiffData:
|
||||
|
||||
def __init__(self, left_path, right_path, available_cols, margin_size):
|
||||
self.left_path, self.right_path = left_path, right_path
|
||||
self.available_cols = available_cols
|
||||
self.margin_size = margin_size
|
||||
self.left_lines, self.right_lines = map(lines_for_path, (left_path, right_path))
|
||||
self.filler_line = render_diff_line('', '', 'filler', margin_size, available_cols)
|
||||
self.left_filler_line = render_diff_line('', '', 'remove', margin_size, available_cols)
|
||||
self.right_filler_line = render_diff_line('', '', 'add', margin_size, available_cols)
|
||||
self.left_hdata = highlights_for_path(left_path)
|
||||
self.right_hdata = highlights_for_path(right_path)
|
||||
|
||||
def left_highlights_for_line(self, line_num):
|
||||
if line_num < len(self.left_hdata):
|
||||
return self.left_hdata[line_num]
|
||||
return []
|
||||
|
||||
def right_highlights_for_line(self, line_num):
|
||||
if line_num < len(self.right_hdata):
|
||||
return self.right_hdata[line_num]
|
||||
return []
|
||||
|
||||
|
||||
def render_diff_line(number, text, ltype, margin_size, available_cols):
|
||||
margin = margin_bg_map[ltype](place_in(number, margin_size))
|
||||
content = text_bg_map[ltype](fill_in(text or '', available_cols))
|
||||
return margin + content
|
||||
|
||||
|
||||
def render_diff_pair(left_line_number, left, left_is_change, right_line_number, right, right_is_change, is_first, margin_size, available_cols):
|
||||
ltype = 'filler' if left_line_number is None else ('remove' if left_is_change else 'context')
|
||||
rtype = 'filler' if right_line_number is None else ('add' if right_is_change else 'context')
|
||||
return (
|
||||
render_diff_line(left_line_number if is_first else None, left, ltype, margin_size, available_cols) +
|
||||
render_diff_line(right_line_number if is_first else None, right, rtype, margin_size, available_cols)
|
||||
)
|
||||
|
||||
|
||||
def hunk_title(hunk_num, hunk, margin_size, available_cols):
|
||||
m = hunk_margin_format(' ' * margin_size)
|
||||
t = '@@ -{},{} +{},{} @@ {}'.format(hunk.left_start + 1, hunk.left_count, hunk.right_start + 1, hunk.right_count, hunk.title)
|
||||
return m + hunk_format(place_in(t, available_cols))
|
||||
|
||||
|
||||
def render_half_line(line_number, line, highlights, ltype, margin_size, available_cols, changed_center=None):
|
||||
bg_highlight = None
|
||||
if changed_center is not None and changed_center[0]:
|
||||
prefix_count, suffix_count = changed_center
|
||||
line_sz = len(line)
|
||||
if prefix_count + suffix_count < line_sz:
|
||||
start, stop = highlight_boundaries(ltype)
|
||||
seg = Segment(prefix_count, start)
|
||||
seg.end = line_sz - suffix_count
|
||||
seg.end_code = stop
|
||||
bg_highlight = seg
|
||||
if highlights or bg_highlight:
|
||||
lines = split_with_highlights(line, available_cols, highlights, bg_highlight)
|
||||
else:
|
||||
lines = split_to_size(line, available_cols)
|
||||
line_number = str(line_number + 1)
|
||||
for line in lines:
|
||||
yield render_diff_line(line_number, line, ltype, margin_size, available_cols)
|
||||
line_number = ''
|
||||
|
||||
|
||||
def lines_for_chunk(data, hunk_num, chunk, chunk_num):
|
||||
if chunk.is_context:
|
||||
for i in range(chunk.left_count):
|
||||
left_line_number = line_ref = chunk.left_start + i
|
||||
right_line_number = chunk.right_start + i
|
||||
highlights = data.left_highlights_for_line(left_line_number)
|
||||
if highlights:
|
||||
lines = split_with_highlights(data.left_lines[left_line_number], data.available_cols, highlights)
|
||||
else:
|
||||
lines = split_to_size(data.left_lines[left_line_number], data.available_cols)
|
||||
left_line_number = str(left_line_number + 1)
|
||||
right_line_number = str(right_line_number + 1)
|
||||
for wli, text in enumerate(lines):
|
||||
line = render_diff_line(left_line_number, text, 'context', data.margin_size, data.available_cols)
|
||||
if right_line_number == left_line_number:
|
||||
r = line
|
||||
else:
|
||||
r = render_diff_line(right_line_number, text, 'context', data.margin_size, data.available_cols)
|
||||
ref = Reference(data.left_path, LineRef(line_ref, wli))
|
||||
yield Line(line + r, ref)
|
||||
left_line_number = right_line_number = ''
|
||||
else:
|
||||
common = min(chunk.left_count, chunk.right_count)
|
||||
for i in range(max(chunk.left_count, chunk.right_count)):
|
||||
ll, rl = [], []
|
||||
if i < chunk.left_count:
|
||||
rln = ref_ln = chunk.left_start + i
|
||||
ll.extend(render_half_line(
|
||||
rln, data.left_lines[rln], data.left_highlights_for_line(rln),
|
||||
'remove', data.margin_size, data.available_cols,
|
||||
None if chunk.centers is None else chunk.centers[i]))
|
||||
ref_path = data.left_path
|
||||
if i < chunk.right_count:
|
||||
rln = ref_ln = chunk.right_start + i
|
||||
rl.extend(render_half_line(
|
||||
rln, data.right_lines[rln], data.right_highlights_for_line(rln),
|
||||
'add', data.margin_size, data.available_cols,
|
||||
None if chunk.centers is None else chunk.centers[i]))
|
||||
ref_path = data.right_path
|
||||
if i < common:
|
||||
extra = len(ll) - len(rl)
|
||||
if extra != 0:
|
||||
if extra < 0:
|
||||
x, fl = ll, data.left_filler_line
|
||||
extra = -extra
|
||||
else:
|
||||
x, fl = rl, data.right_filler_line
|
||||
x.extend(repeat(fl, extra))
|
||||
else:
|
||||
if ll:
|
||||
x, count = rl, len(ll)
|
||||
else:
|
||||
x, count = ll, len(rl)
|
||||
x.extend(repeat(data.filler_line, count))
|
||||
for wli, (left_line, right_line) in enumerate(zip(ll, rl)):
|
||||
ref = Reference(ref_path, LineRef(ref_ln, wli))
|
||||
yield Line(left_line + right_line, ref, i == 0 and wli == 0)
|
||||
|
||||
|
||||
def lines_for_diff(left_path, right_path, hunks, args, columns, margin_size):
|
||||
available_cols = columns // 2 - margin_size
|
||||
data = DiffData(left_path, right_path, available_cols, margin_size)
|
||||
|
||||
for hunk_num, hunk in enumerate(hunks):
|
||||
yield Line(hunk_title(hunk_num, hunk, margin_size, columns - margin_size), Reference(left_path, LineRef(hunk.left_start)))
|
||||
for cnum, chunk in enumerate(hunk.chunks):
|
||||
yield from lines_for_chunk(data, hunk_num, chunk, cnum)
|
||||
|
||||
|
||||
def all_lines(path, args, columns, margin_size, is_add=True):
|
||||
available_cols = columns // 2 - margin_size
|
||||
ltype = 'add' if is_add else 'remove'
|
||||
lines = lines_for_path(path)
|
||||
filler = render_diff_line('', '', 'filler', margin_size, available_cols)
|
||||
msg_written = False
|
||||
hdata = highlights_for_path(path)
|
||||
|
||||
def highlights(num):
|
||||
return hdata[num] if num < len(hdata) else []
|
||||
|
||||
for line_number, line in enumerate(lines):
|
||||
h = render_half_line(line_number, line, highlights(line_number), ltype, margin_size, available_cols)
|
||||
for i, hl in enumerate(h):
|
||||
ref = Reference(path, LineRef(line_number, i))
|
||||
empty = filler
|
||||
if not msg_written:
|
||||
msg_written = True
|
||||
empty = render_diff_line(
|
||||
'', _('This file was added') if is_add else _('This file was removed'),
|
||||
'filler', margin_size, available_cols)
|
||||
text = (empty + hl) if is_add else (hl + empty)
|
||||
yield Line(text, ref, line_number == 0 and i == 0)
|
||||
|
||||
|
||||
def rename_lines(path, other_path, args, columns, margin_size):
|
||||
m = ' ' * margin_size
|
||||
for line in split_to_size(_('The file {0} was renamed to {1}').format(
|
||||
sanitize(path_name_map[path]), sanitize(path_name_map[other_path])), columns - margin_size):
|
||||
yield m + line
|
||||
|
||||
|
||||
class Image:
|
||||
|
||||
def __init__(self, image_id, width, height, margin_size):
|
||||
self.image_id = image_id
|
||||
self.width, self.height = width, height
|
||||
ss = screen_size()
|
||||
self.rows = int(ceil(self.height / ss.cell_height))
|
||||
self.columns = int(ceil(self.width / ss.cell_width))
|
||||
self.margin_size = margin_size
|
||||
|
||||
|
||||
class ImagePlacement:
|
||||
|
||||
def __init__(self, image, row):
|
||||
self.image = image
|
||||
self.row = row
|
||||
|
||||
|
||||
def render_image(path, is_left, available_cols, margin_size, image_manager):
|
||||
lnum = 0
|
||||
margin_fmt = removed_margin_format if is_left else added_margin_format
|
||||
m = margin_fmt(' ' * margin_size)
|
||||
fmt = removed_format if is_left else added_format
|
||||
|
||||
def yield_split(text):
|
||||
nonlocal lnum
|
||||
for i, line in enumerate(split_to_size(text, available_cols)):
|
||||
yield m + fmt(place_in(line, available_cols)), Reference(path, LineRef(lnum, i)), None
|
||||
lnum += 1
|
||||
|
||||
try:
|
||||
image_id, width, height = image_manager.send_image(path, available_cols - margin_size, screen_size().rows - 2)
|
||||
except Exception as e:
|
||||
yield from yield_split(_('Failed to render image, with error:'))
|
||||
yield from yield_split(' '.join(str(e).splitlines()))
|
||||
return
|
||||
meta = _('Dimensions: {0}x{1} pixels Size: {2}').format(
|
||||
width, height, human_readable(len(data_for_path(path))))
|
||||
yield from yield_split(meta)
|
||||
bg_line = m + fmt(' ' * available_cols)
|
||||
img = Image(image_id, width, height, margin_size)
|
||||
for r in range(img.rows):
|
||||
yield bg_line, Reference(path, LineRef(lnum)), ImagePlacement(img, r)
|
||||
lnum += 1
|
||||
|
||||
|
||||
def image_lines(left_path, right_path, columns, margin_size, image_manager):
|
||||
available_cols = columns // 2 - margin_size
|
||||
left_lines, right_lines = iter(()), iter(())
|
||||
if left_path is not None:
|
||||
left_lines = render_image(left_path, True, available_cols, margin_size, image_manager)
|
||||
if right_path is not None:
|
||||
right_lines = render_image(right_path, False, available_cols, margin_size, image_manager)
|
||||
filler = ' ' * (available_cols + margin_size)
|
||||
is_change_start = True
|
||||
for left, right in zip_longest(left_lines, right_lines):
|
||||
left_placement = right_placement = None
|
||||
if left is None:
|
||||
left = filler
|
||||
right, ref, right_placement = right
|
||||
elif right is None:
|
||||
right = filler
|
||||
left, ref, left_placement = left
|
||||
else:
|
||||
right, ref, right_placement = right
|
||||
left, ref, left_placement = left
|
||||
image_data = (left_placement, right_placement) if left_placement or right_placement else None
|
||||
yield Line(left + right, ref, is_change_start, image_data)
|
||||
is_change_start = False
|
||||
|
||||
|
||||
def render_diff(collection, diff_map, args, columns, image_manager):
|
||||
largest_line_number = 0
|
||||
for path, item_type, other_path in collection:
|
||||
if item_type == 'diff':
|
||||
patch = diff_map.get(path)
|
||||
if patch is not None:
|
||||
largest_line_number = max(largest_line_number, patch.largest_line_number)
|
||||
|
||||
margin_size = max(3, len(str(largest_line_number)) + 1)
|
||||
last_item_num = len(collection) - 1
|
||||
|
||||
for i, (path, item_type, other_path) in enumerate(collection):
|
||||
item_ref = Reference(path)
|
||||
is_binary = isinstance(data_for_path(path), bytes)
|
||||
is_img = is_binary and is_image(path) and images_supported()
|
||||
yield from yield_lines_from(title_lines(path, other_path, args, columns, margin_size), item_ref, False)
|
||||
if item_type == 'diff':
|
||||
if is_binary:
|
||||
if is_img:
|
||||
ans = image_lines(path, other_path, columns, margin_size, image_manager)
|
||||
else:
|
||||
ans = yield_lines_from(binary_lines(path, other_path, columns, margin_size), item_ref)
|
||||
else:
|
||||
ans = lines_for_diff(path, other_path, diff_map[path], args, columns, margin_size)
|
||||
elif item_type == 'add':
|
||||
if is_binary:
|
||||
if is_img:
|
||||
ans = image_lines(None, path, columns, margin_size, image_manager)
|
||||
else:
|
||||
ans = yield_lines_from(binary_lines(None, path, columns, margin_size), item_ref)
|
||||
else:
|
||||
ans = all_lines(path, args, columns, margin_size, is_add=True)
|
||||
elif item_type == 'removal':
|
||||
if is_binary:
|
||||
if is_img:
|
||||
ans = image_lines(path, None, columns, margin_size, image_manager)
|
||||
else:
|
||||
ans = yield_lines_from(binary_lines(path, None, columns, margin_size), item_ref)
|
||||
else:
|
||||
ans = all_lines(path, args, columns, margin_size, is_add=False)
|
||||
elif item_type == 'rename':
|
||||
ans = yield_lines_from(rename_lines(path, other_path, args, columns, margin_size), item_ref)
|
||||
else:
|
||||
raise ValueError('Unsupported item type: {}'.format(item_type))
|
||||
yield from ans
|
||||
if i < last_item_num:
|
||||
yield Line('', item_ref)
|
||||
179
kittens/diff/speedup.c
Normal file
179
kittens/diff/speedup.c
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* speedup.c
|
||||
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include "data-types.h"
|
||||
|
||||
static PyObject*
|
||||
changed_center(PyObject *self UNUSED, PyObject *args) {
|
||||
unsigned int prefix_count = 0, suffix_count = 0;
|
||||
PyObject *lp, *rp;
|
||||
if (!PyArg_ParseTuple(args, "UU", &lp, &rp)) return NULL;
|
||||
const size_t left_len = PyUnicode_GET_LENGTH(lp), right_len = PyUnicode_GET_LENGTH(rp);
|
||||
|
||||
#define R(which, index) PyUnicode_READ(PyUnicode_KIND(which), PyUnicode_DATA(which), index)
|
||||
while(prefix_count < MIN(left_len, right_len)) {
|
||||
if (R(lp, prefix_count) != R(rp, prefix_count)) break;
|
||||
prefix_count++;
|
||||
}
|
||||
if (left_len && right_len && prefix_count < MIN(left_len, right_len)) {
|
||||
while(suffix_count < MIN(left_len - prefix_count, right_len - prefix_count)) {
|
||||
if(R(lp, left_len - 1 - suffix_count) != R(rp, right_len - 1 - suffix_count)) break;
|
||||
suffix_count++;
|
||||
}
|
||||
}
|
||||
#undef R
|
||||
return Py_BuildValue("II", prefix_count, suffix_count);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
unsigned int start_pos, end_pos, current_pos;
|
||||
PyObject *start_code, *end_code;
|
||||
} Segment;
|
||||
|
||||
typedef struct {
|
||||
Segment sg;
|
||||
unsigned int num, pos;
|
||||
} SegmentPointer;
|
||||
|
||||
static const Segment EMPTY_SEGMENT = { .current_pos = UINT_MAX };
|
||||
|
||||
static inline bool
|
||||
convert_segment(PyObject *highlight, Segment *dest) {
|
||||
PyObject *val = NULL;
|
||||
#define I
|
||||
#define A(x, d, c) { \
|
||||
val = PyObject_GetAttrString(highlight, #x); \
|
||||
if (val == NULL) return false; \
|
||||
dest->d = c(val); Py_DECREF(val); \
|
||||
}
|
||||
A(start, start_pos, PyLong_AsUnsignedLong);
|
||||
A(end, end_pos, PyLong_AsUnsignedLong);
|
||||
dest->current_pos = dest->start_pos;
|
||||
A(start_code, start_code, I);
|
||||
A(end_code, end_code, I);
|
||||
if (!PyUnicode_Check(dest->start_code)) { PyErr_SetString(PyExc_TypeError, "start_code is not a string"); return false; }
|
||||
if (!PyUnicode_Check(dest->end_code)) { PyErr_SetString(PyExc_TypeError, "end_code is not a string"); return false; }
|
||||
#undef A
|
||||
#undef I
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
next_segment(SegmentPointer *s, PyObject *highlights) {
|
||||
if (s->pos < s->num) {
|
||||
if (!convert_segment(PyList_GET_ITEM(highlights, s->pos), &s->sg)) return false;
|
||||
s->pos++;
|
||||
} else s->sg.current_pos = UINT_MAX;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
insert_code(PyObject *code, Py_UCS4 *buf, size_t bufsz, unsigned int *buf_pos) {
|
||||
unsigned int csz = PyUnicode_GET_LENGTH(code);
|
||||
if (*buf_pos + csz >= bufsz) return false;
|
||||
for (unsigned int s = 0; s < csz; s++) buf[(*buf_pos)++] = PyUnicode_READ(PyUnicode_KIND(code), PyUnicode_DATA(code), s);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
add_line(Segment *bg_segment, Segment *fg_segment, Py_UCS4 *buf, size_t bufsz, unsigned int *buf_pos, PyObject *ans) {
|
||||
bool bg_is_active = bg_segment->current_pos == bg_segment->end_pos, fg_is_active = fg_segment->current_pos == fg_segment->end_pos;
|
||||
if (bg_is_active) { if(!insert_code(bg_segment->end_code, buf, bufsz, buf_pos)) return false; }
|
||||
if (fg_is_active) { if(!insert_code(fg_segment->end_code, buf, bufsz, buf_pos)) return false; }
|
||||
PyObject *wl = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, *buf_pos);
|
||||
if (!wl) return false;
|
||||
int ret = PyList_Append(ans, wl); Py_DECREF(wl); if (ret != 0) return false;
|
||||
*buf_pos = 0;
|
||||
if (bg_is_active) { if(!insert_code(bg_segment->start_code, buf, bufsz, buf_pos)) return false; }
|
||||
if (fg_is_active) { if(!insert_code(fg_segment->start_code, buf, bufsz, buf_pos)) return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
split_with_highlights(PyObject *self UNUSED, PyObject *args) {
|
||||
PyObject *line, *truncate_points_py, *fg_highlights, *bg_highlight;
|
||||
if (!PyArg_ParseTuple(args, "UO!O!O", &line, &PyList_Type, &truncate_points_py, &PyList_Type, &fg_highlights, &bg_highlight)) return NULL;
|
||||
PyObject *ans = PyList_New(0);
|
||||
if (!ans) return NULL;
|
||||
static unsigned int truncate_points[256];
|
||||
unsigned int num_truncate_pts = PyList_GET_SIZE(truncate_points_py), truncate_pos = 0, truncate_point;
|
||||
for (unsigned int i = 0; i < MIN(num_truncate_pts, arraysz(truncate_points)); i++) {
|
||||
truncate_points[i] = PyLong_AsUnsignedLong(PyList_GET_ITEM(truncate_points_py, i));
|
||||
}
|
||||
SegmentPointer fg_segment = { .sg = EMPTY_SEGMENT, .num = PyList_GET_SIZE(fg_highlights)}, bg_segment = { .sg = EMPTY_SEGMENT };
|
||||
if (bg_highlight != Py_None) { if (!convert_segment(bg_highlight, &bg_segment.sg)) { Py_CLEAR(ans); return NULL; }; bg_segment.num = 1; }
|
||||
#define CHECK_CALL(func, ...) if (!func(__VA_ARGS__)) { Py_CLEAR(ans); if (!PyErr_Occurred()) PyErr_SetString(PyExc_ValueError, "line too long"); return NULL; }
|
||||
CHECK_CALL(next_segment, &fg_segment, fg_highlights);
|
||||
|
||||
#define NEXT_TRUNCATE_POINT truncate_point = (truncate_pos < num_truncate_pts) ? truncate_points[truncate_pos++] : UINT_MAX
|
||||
NEXT_TRUNCATE_POINT;
|
||||
|
||||
#define INSERT_CODE(x) { CHECK_CALL(insert_code, x, buf, arraysz(buf), &buf_pos); }
|
||||
|
||||
#define ADD_LINE CHECK_CALL(add_line, &bg_segment.sg, &fg_segment.sg, buf, arraysz(buf), &buf_pos, ans);
|
||||
|
||||
#define ADD_CHAR(x) { \
|
||||
buf[buf_pos++] = x; \
|
||||
if (buf_pos >= arraysz(buf)) { Py_CLEAR(ans); PyErr_SetString(PyExc_ValueError, "line too long"); return NULL; } \
|
||||
}
|
||||
#define CHECK_SEGMENT(sgp, is_fg) { \
|
||||
if (i == sgp.sg.current_pos) { \
|
||||
INSERT_CODE(sgp.sg.current_pos == sgp.sg.start_pos ? sgp.sg.start_code : sgp.sg.end_code); \
|
||||
if (sgp.sg.current_pos == sgp.sg.start_pos) sgp.sg.current_pos = sgp.sg.end_pos; \
|
||||
else { \
|
||||
if (is_fg) { \
|
||||
CHECK_CALL(next_segment, &fg_segment, fg_highlights); \
|
||||
if (sgp.sg.current_pos == i) { \
|
||||
INSERT_CODE(sgp.sg.start_code); \
|
||||
sgp.sg.current_pos = sgp.sg.end_pos; \
|
||||
} \
|
||||
} else sgp.sg.current_pos = UINT_MAX; \
|
||||
} \
|
||||
}\
|
||||
}
|
||||
|
||||
const unsigned int line_sz = PyUnicode_GET_LENGTH(line);
|
||||
static Py_UCS4 buf[4096];
|
||||
unsigned int i = 0, buf_pos = 0;
|
||||
for (; i < line_sz; i++) {
|
||||
if (i == truncate_point) { ADD_LINE; NEXT_TRUNCATE_POINT; }
|
||||
CHECK_SEGMENT(bg_segment, false);
|
||||
CHECK_SEGMENT(fg_segment, true)
|
||||
ADD_CHAR(PyUnicode_READ(PyUnicode_KIND(line), PyUnicode_DATA(line), i));
|
||||
}
|
||||
if (buf_pos) ADD_LINE;
|
||||
return ans;
|
||||
#undef INSERT_CODE
|
||||
#undef CHECK_SEGMENT
|
||||
#undef CHECK_CALL
|
||||
#undef ADD_CHAR
|
||||
#undef ADD_LINE
|
||||
#undef NEXT_TRUNCATE_POINT
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"changed_center", (PyCFunction)changed_center, METH_VARARGS, ""},
|
||||
{"split_with_highlights", (PyCFunction)split_with_highlights, METH_VARARGS, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
static struct PyModuleDef module = {
|
||||
.m_base = PyModuleDef_HEAD_INIT,
|
||||
.m_name = "diff_speedup", /* name of module */
|
||||
.m_doc = NULL,
|
||||
.m_size = -1,
|
||||
.m_methods = module_methods
|
||||
};
|
||||
|
||||
EXPORTED PyMODINIT_FUNC
|
||||
PyInit_diff_speedup(void) {
|
||||
PyObject *m;
|
||||
|
||||
m = PyModule_Create(&module);
|
||||
if (m == NULL) return NULL;
|
||||
return m;
|
||||
}
|
||||
@@ -12,15 +12,15 @@ from gettext import gettext as _
|
||||
from kitty.cli import parse_args
|
||||
from kitty.fast_data_types import set_clipboard_string
|
||||
from kitty.key_encoding import ESCAPE, backspace_key, enter_key
|
||||
from kitty.utils import screen_size_function
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import (
|
||||
clear_screen, faint, set_cursor_visible, set_window_title, styled
|
||||
)
|
||||
from ..tui.operations import faint, styled
|
||||
|
||||
URL_PREFIXES = 'http https file ftp'.split()
|
||||
HINT_ALPHABET = string.digits + string.ascii_lowercase
|
||||
screen_size = screen_size_function()
|
||||
|
||||
|
||||
class Mark(object):
|
||||
@@ -46,50 +46,37 @@ def decode_hint(x):
|
||||
return int(x, 36)
|
||||
|
||||
|
||||
def render(lines, current_input):
|
||||
ans = []
|
||||
def highlight_mark(m, text, current_input):
|
||||
hint = encode_hint(m.index)
|
||||
if current_input and not hint.startswith(current_input):
|
||||
return faint(text)
|
||||
hint = hint[len(current_input):] or ' '
|
||||
text = text[len(hint):]
|
||||
return styled(
|
||||
hint,
|
||||
fg='black',
|
||||
bg='green',
|
||||
bold=True
|
||||
) + styled(
|
||||
text, fg='gray', fg_intense=True, bold=True
|
||||
)
|
||||
|
||||
def mark(m):
|
||||
hint = encode_hint(m.index)
|
||||
text = m.text
|
||||
if current_input and not hint.startswith(current_input):
|
||||
return faint(text)
|
||||
hint = hint[len(current_input):] or ' '
|
||||
text = text[len(hint):]
|
||||
return styled(
|
||||
hint,
|
||||
fg='black',
|
||||
bg='green',
|
||||
bold=True
|
||||
) + styled(
|
||||
text, fg='gray', fg_intense=True, bold=True
|
||||
)
|
||||
|
||||
for line, marks in lines:
|
||||
if not marks:
|
||||
ans.append(faint(line))
|
||||
continue
|
||||
buf = []
|
||||
def render(text, current_input, all_marks):
|
||||
for mark in reversed(all_marks):
|
||||
mtext = highlight_mark(mark, text[mark.start:mark.end], current_input)
|
||||
text = text[:mark.start] + mtext + text[mark.end:]
|
||||
|
||||
for i, m in enumerate(marks):
|
||||
if i == 0 and m.start:
|
||||
buf.append(faint(line[:m.start]))
|
||||
buf.append(mark(m))
|
||||
if m is not marks[-1]:
|
||||
buf.append(faint(line[m.end:marks[i + 1].start]))
|
||||
text = text.replace('\0', '')
|
||||
|
||||
rest = line[marks[-1].end:]
|
||||
if rest:
|
||||
buf.append(faint(rest))
|
||||
|
||||
ans.append(''.join(buf))
|
||||
return '\r\n'.join(ans)
|
||||
return text.replace('\n', '\r\n').rstrip()
|
||||
|
||||
|
||||
class Hints(Handler):
|
||||
|
||||
def __init__(self, lines, index_map, args):
|
||||
self.lines, self.index_map = tuple(lines), index_map
|
||||
def __init__(self, text, all_marks, index_map, args):
|
||||
self.text, self.index_map = text, index_map
|
||||
self.all_marks = all_marks
|
||||
self.current_input = ''
|
||||
self.current_text = None
|
||||
self.args = args
|
||||
@@ -97,11 +84,11 @@ class Hints(Handler):
|
||||
self.chosen = None
|
||||
|
||||
def init_terminal_state(self):
|
||||
self.write(set_cursor_visible(False))
|
||||
self.write(set_window_title(self.window_title))
|
||||
self.cmd.set_cursor_visible(False)
|
||||
self.cmd.set_window_title(self.window_title)
|
||||
self.cmd.set_line_wrapping(False)
|
||||
|
||||
def initialize(self, *args):
|
||||
Handler.initialize(self, *args)
|
||||
def initialize(self):
|
||||
self.init_terminal_state()
|
||||
self.draw_screen()
|
||||
|
||||
@@ -129,9 +116,15 @@ class Hints(Handler):
|
||||
self.current_text = None
|
||||
self.draw_screen()
|
||||
elif key_event is enter_key and self.current_input:
|
||||
idx = decode_hint(self.current_input)
|
||||
self.chosen = self.index_map[idx].text
|
||||
self.quit_loop(0)
|
||||
try:
|
||||
idx = decode_hint(self.current_input)
|
||||
self.chosen = self.index_map[idx].text
|
||||
except Exception:
|
||||
self.current_input = ''
|
||||
self.current_text = None
|
||||
self.draw_screen()
|
||||
else:
|
||||
self.quit_loop(0)
|
||||
elif key_event.key is ESCAPE:
|
||||
self.quit_loop(1)
|
||||
|
||||
@@ -147,44 +140,82 @@ class Hints(Handler):
|
||||
|
||||
def draw_screen(self):
|
||||
if self.current_text is None:
|
||||
self.current_text = render(self.lines, self.current_input)
|
||||
self.write(clear_screen())
|
||||
self.current_text = render(self.text, self.current_input, self.all_marks)
|
||||
self.cmd.clear_screen()
|
||||
self.write(self.current_text)
|
||||
|
||||
|
||||
def regex_finditer(pat, minimum_match_length, line):
|
||||
for m in pat.finditer(line):
|
||||
def regex_finditer(pat, minimum_match_length, text):
|
||||
for m in pat.finditer(text):
|
||||
s, e = m.span(pat.groups)
|
||||
while e > s + 1 and text[e-1] == '\0':
|
||||
e -= 1
|
||||
if e - s >= minimum_match_length:
|
||||
yield s, e
|
||||
|
||||
|
||||
def find_urls(pat, line):
|
||||
for m in pat.finditer(line):
|
||||
s, e = m.span()
|
||||
if s > 4 and line[s - 5:s] == 'link:': # asciidoc URLs
|
||||
url = line[s:e]
|
||||
idx = url.rfind('[')
|
||||
if idx > -1:
|
||||
e -= len(url) - idx
|
||||
while line[e - 1] in '.,?!' and e > 1: # remove trailing punctuation
|
||||
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>'}
|
||||
opening_brackets = ''.join(closing_bracket_map)
|
||||
postprocessor_map = {}
|
||||
|
||||
|
||||
def postprocessor(func):
|
||||
postprocessor_map[func.__name__] = func
|
||||
return func
|
||||
|
||||
|
||||
@postprocessor
|
||||
def url(text, s, e):
|
||||
if s > 4 and text[s - 5:s] == 'link:': # asciidoc URLs
|
||||
url = text[s:e]
|
||||
idx = url.rfind('[')
|
||||
if idx > -1:
|
||||
e -= len(url) - idx
|
||||
while text[e - 1] in '.,?!' and e > 1: # remove trailing punctuation
|
||||
e -= 1
|
||||
# Remove trailing bracket if matched by leading bracket
|
||||
if s > 0 and e < len(text) and text[s-1] in opening_brackets and text[e-1] == closing_bracket_map[text[s-1]]:
|
||||
e -= 1
|
||||
# Remove trailing quote if matched by leading quote
|
||||
if s > 0 and e < len(text) and text[s-1] in '\'"' and text[e-1] == text[s-1]:
|
||||
e -= 1
|
||||
return s, e
|
||||
|
||||
|
||||
@postprocessor
|
||||
def brackets(text, s, e):
|
||||
# Remove matching brackets
|
||||
if e > s and e <= len(text):
|
||||
before = text[s]
|
||||
if before in '({[<' and text[e-1] == closing_bracket_map[before]:
|
||||
s += 1
|
||||
e -= 1
|
||||
yield s, e
|
||||
return s, e
|
||||
|
||||
|
||||
def mark(finditer, line, all_marks):
|
||||
marks = []
|
||||
for s, e in finditer(line):
|
||||
idx = len(all_marks)
|
||||
text = line[s:e]
|
||||
marks.append(Mark(idx, s, e, text))
|
||||
all_marks.append(marks[-1])
|
||||
return line, marks
|
||||
@postprocessor
|
||||
def quotes(text, s, e):
|
||||
# Remove matching quotes
|
||||
if e > s and e <= len(text):
|
||||
before = text[s]
|
||||
if before in '\'"' and text[e-1] == before:
|
||||
s += 1
|
||||
e -= 1
|
||||
return s, e
|
||||
|
||||
|
||||
def run_loop(args, lines, index_map):
|
||||
def mark(pattern, post_processors, text, args):
|
||||
pat = re.compile(pattern)
|
||||
for idx, (s, e) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
|
||||
for func in post_processors:
|
||||
s, e = func(text, s, e)
|
||||
mark_text = text[s:e].replace('\n', '').replace('\0', '')
|
||||
yield Mark(idx, s, e, mark_text)
|
||||
|
||||
|
||||
def run_loop(args, text, all_marks, index_map):
|
||||
loop = Loop()
|
||||
handler = Hints(lines, index_map, args)
|
||||
handler = Hints(text, all_marks, index_map, args)
|
||||
loop.loop(handler)
|
||||
if handler.chosen and loop.return_code == 0:
|
||||
return {'match': handler.chosen, 'program': args.program}
|
||||
@@ -195,45 +226,74 @@ def escape(chars):
|
||||
return chars.replace('\\', '\\\\').replace('-', r'\-').replace(']', r'\]')
|
||||
|
||||
|
||||
def run(args, text):
|
||||
def functions_for(args):
|
||||
post_processors = []
|
||||
if args.type == 'url':
|
||||
from .url_regex import url_delimiters
|
||||
url_pat = '(?:{})://[^{}]{{3,}}'.format(
|
||||
pattern = '(?:{})://[^{}]{{3,}}'.format(
|
||||
'|'.join(args.url_prefixes.split(',')), url_delimiters
|
||||
)
|
||||
finditer = partial(find_urls, re.compile(url_pat))
|
||||
post_processors.append(url)
|
||||
elif args.type == 'path':
|
||||
finditer = partial(regex_finditer, re.compile(r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,5})'), args.minimum_match_length)
|
||||
pattern = r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,5})'
|
||||
post_processors.extend((brackets, quotes))
|
||||
elif args.type == 'line':
|
||||
finditer = partial(regex_finditer, re.compile(r'(?m)^\s*(.+)\s*$'), args.minimum_match_length)
|
||||
pattern = '(?m)^\\s*(.+)[\\s\0]*$'
|
||||
elif args.type == 'word':
|
||||
chars = args.word_characters
|
||||
if chars is None:
|
||||
import json
|
||||
chars = json.loads(os.environ['KITTY_COMMON_OPTS'])['select_by_word_characters']
|
||||
pat = re.compile('(?u)[{}\w]{{{},}}'.format(escape(chars), args.minimum_match_length))
|
||||
finditer = partial(regex_finditer, pat, args.minimum_match_length)
|
||||
pattern = r'(?u)[{}\w]{{{},}}'.format(escape(chars), args.minimum_match_length)
|
||||
post_processors.extend((brackets, quotes))
|
||||
else:
|
||||
finditer = partial(regex_finditer, re.compile(args.regex), args.minimum_match_length)
|
||||
pattern = args.regex
|
||||
return pattern, post_processors
|
||||
|
||||
|
||||
def convert_text(text, cols):
|
||||
lines = []
|
||||
all_marks = []
|
||||
for line in text.splitlines():
|
||||
marked = mark(finditer, line, all_marks)
|
||||
lines.append(marked)
|
||||
if not all_marks:
|
||||
input(_('No {} found, press Enter to abort.').format(
|
||||
'URLs' if args.type == 'url' else 'matches'
|
||||
))
|
||||
return
|
||||
|
||||
largest_index = all_marks[-1].index
|
||||
for m in all_marks:
|
||||
m.index = largest_index - m.index
|
||||
index_map = {m.index: m for m in all_marks}
|
||||
|
||||
return run_loop(args, lines, index_map)
|
||||
for full_line in text.split('\n'):
|
||||
if full_line:
|
||||
for line in full_line.split('\r'):
|
||||
if line:
|
||||
lines.append(line.ljust(cols, '\0'))
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def parse_input(text):
|
||||
try:
|
||||
cols = int(os.environ['OVERLAID_WINDOW_COLS'])
|
||||
except KeyError:
|
||||
cols = screen_size().cols
|
||||
return convert_text(text, cols)
|
||||
|
||||
|
||||
def run(args, text):
|
||||
try:
|
||||
pattern, post_processors = functions_for(args)
|
||||
text = parse_input(text)
|
||||
all_marks = tuple(mark(pattern, post_processors, text, args))
|
||||
if not all_marks:
|
||||
input(_('No {} found, press Enter to quit.').format(
|
||||
'URLs' if args.type == 'url' else 'matches'
|
||||
))
|
||||
return
|
||||
|
||||
largest_index = all_marks[-1].index
|
||||
for m in all_marks:
|
||||
m.index = largest_index - m.index
|
||||
index_map = {m.index: m for m in all_marks}
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
input('Press Enter to quit.')
|
||||
raise SystemExit(1)
|
||||
|
||||
return run_loop(args, text, all_marks, index_map)
|
||||
|
||||
|
||||
# CLI {{{
|
||||
OPTIONS = partial(r'''
|
||||
--program
|
||||
default=default
|
||||
@@ -261,7 +321,7 @@ Comma separated list of recognized URL prefixes.
|
||||
|
||||
|
||||
--word-characters
|
||||
Characters to consider as part of a word. In addition, all chacraters marked as
|
||||
Characters to consider as part of a word. In addition, all characters marked as
|
||||
alpha-numeric in the unicode database will be considered as word characters.
|
||||
Defaults to the select_by_word_characters setting from kitty.conf.
|
||||
|
||||
@@ -273,8 +333,12 @@ The minimum number of characters to consider a match.
|
||||
'''.format, ','.join(sorted(URL_PREFIXES)))
|
||||
|
||||
|
||||
def main(args):
|
||||
def parse_hints_args(args):
|
||||
msg = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
|
||||
return parse_args(args, OPTIONS, '', msg, 'hints')
|
||||
|
||||
|
||||
def main(args):
|
||||
text = ''
|
||||
if sys.stdin.isatty():
|
||||
if '--help' not in args and '-h' not in args:
|
||||
@@ -285,7 +349,7 @@ def main(args):
|
||||
text = sys.stdin.buffer.read().decode('utf-8')
|
||||
sys.stdin = open('/dev/tty')
|
||||
try:
|
||||
args, items = parse_args(args[1:], OPTIONS, '', msg, 'hints')
|
||||
args, items = parse_hints_args(args[1:])
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0], file=sys.stderr)
|
||||
@@ -307,7 +371,14 @@ def handle_result(args, data, target_window_id, boss):
|
||||
elif program == '@':
|
||||
set_clipboard_string(data['match'])
|
||||
else:
|
||||
boss.open_url(data['match'], None if program == 'default' else program)
|
||||
cwd = None
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
if w is not None:
|
||||
cwd = w.cwd_of_child
|
||||
boss.open_url(data['match'], None if program == 'default' else program, cwd=cwd)
|
||||
|
||||
|
||||
handle_result.type_of_input = 'screen'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -315,3 +386,4 @@ if __name__ == '__main__':
|
||||
ans = main(sys.argv)
|
||||
if ans:
|
||||
print(ans)
|
||||
# }}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
url_delimiters = '\x00-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa
|
||||
url_delimiters = '\x00-\x09\x0b-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa
|
||||
0
kittens/icat/__init__.py
Normal file
0
kittens/icat/__init__.py
Normal file
@@ -2,41 +2,26 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import array
|
||||
import codecs
|
||||
import fcntl
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import termios
|
||||
import zlib
|
||||
from base64 import standard_b64encode
|
||||
from collections import namedtuple
|
||||
from math import ceil, floor
|
||||
from math import ceil
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.constants import appname
|
||||
from kitty.utils import read_with_timeout
|
||||
|
||||
try:
|
||||
fsenc = sys.getfilesystemencoding() or 'utf-8'
|
||||
codecs.lookup(fsenc)
|
||||
except Exception:
|
||||
fsenc = 'utf-8'
|
||||
|
||||
|
||||
class OpenFailed(ValueError):
|
||||
|
||||
def __init__(self, path, message):
|
||||
ValueError.__init__(
|
||||
self, 'Failed to open: {} with error: {}'.format(path, message)
|
||||
)
|
||||
self.path = path
|
||||
from kitty.utils import fit_image, read_with_timeout
|
||||
|
||||
from ..tui.images import (
|
||||
ConvertFailed, NoImageMagick, OpenFailed, convert, fsenc, identify,
|
||||
screen_size
|
||||
)
|
||||
from ..tui.operations import clear_images_on_screen, serialize_gr_command
|
||||
|
||||
OPTIONS = '''\
|
||||
--align
|
||||
@@ -100,46 +85,11 @@ def options_spec():
|
||||
return options_spec.ans
|
||||
|
||||
|
||||
Size = namedtuple('Size', 'rows cols width height')
|
||||
|
||||
|
||||
def screen_size():
|
||||
if screen_size.changed:
|
||||
buf = array.array('H', [0, 0, 0, 0])
|
||||
fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, buf)
|
||||
screen_size.ans = Size(*buf)
|
||||
screen_size.changed = False
|
||||
return screen_size.ans
|
||||
|
||||
|
||||
screen_size.changed = True
|
||||
|
||||
|
||||
def write_gr_cmd(cmd, payload=None):
|
||||
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
|
||||
w = sys.stdout.buffer.write
|
||||
w(b'\033_G'), w(cmd.encode('ascii'))
|
||||
if payload:
|
||||
w(b';')
|
||||
w(payload)
|
||||
w(b'\033\\')
|
||||
sys.stdout.buffer.write(serialize_gr_command(cmd, payload))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def fit_image(width, height, pwidth, pheight):
|
||||
if height > pheight:
|
||||
corrf = pheight / float(height)
|
||||
width, height = floor(corrf * width), pheight
|
||||
if width > pwidth:
|
||||
corrf = pwidth / float(width)
|
||||
width, height = pwidth, floor(corrf * height)
|
||||
if height > pheight:
|
||||
corrf = pheight / float(height)
|
||||
width, height = floor(corrf * width), pheight
|
||||
|
||||
return int(width), int(height)
|
||||
|
||||
|
||||
def calculate_in_cell_x_offset(width, cell_width, align):
|
||||
if align == 'left':
|
||||
return 0
|
||||
@@ -217,52 +167,6 @@ def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=N
|
||||
write_chunked(cmd, data)
|
||||
|
||||
|
||||
ImageData = namedtuple('ImageData', 'fmt width height mode')
|
||||
|
||||
|
||||
def run_imagemagick(path, cmd, keep_stdout=True):
|
||||
try:
|
||||
p = subprocess.run(cmd, stdout=subprocess.PIPE if keep_stdout else subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
except FileNotFoundError:
|
||||
raise SystemExit('ImageMagick is required to cat images')
|
||||
if p.returncode != 0:
|
||||
raise OpenFailed(path, p.stderr.decode('utf-8'))
|
||||
return p
|
||||
|
||||
|
||||
def identify(path):
|
||||
p = run_imagemagick(path, ['identify', '-format', '%m %w %h %A', path])
|
||||
parts = tuple(filter(None, p.stdout.decode('utf-8').split()))
|
||||
mode = 'rgb' if parts[3].lower() == 'false' else 'rgba'
|
||||
return ImageData(parts[0].lower(), int(parts[1]), int(parts[2]), mode)
|
||||
|
||||
|
||||
def convert(path, m, available_width, available_height, scale_up):
|
||||
width, height = m.width, m.height
|
||||
cmd = ['convert', '-background', 'none', path]
|
||||
if scale_up:
|
||||
if width < available_width:
|
||||
r = available_width / width
|
||||
width, height = available_width, int(height * r)
|
||||
if width > available_width or height > available_height:
|
||||
width, height = fit_image(width, height, available_width, available_height)
|
||||
cmd += ['-resize', '{}x{}'.format(width, height)]
|
||||
with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False) as outfile:
|
||||
run_imagemagick(path, cmd + [outfile.name])
|
||||
# ImageMagick sometimes generated rgba images smaller than the specified
|
||||
# size. See https://github.com/kovidgoyal/kitty/issues/276 for examples
|
||||
sz = os.path.getsize(outfile.name)
|
||||
bytes_per_pixel = 3 if m.mode == 'rgb' else 4
|
||||
expected_size = bytes_per_pixel * width * height
|
||||
if sz < expected_size:
|
||||
missing = expected_size - sz
|
||||
if missing % (bytes_per_pixel * width) != 0:
|
||||
raise SystemExit('ImageMagick failed to convert {} correctly, it generated {} < {} of data'.format(path, sz, expected_size))
|
||||
height -= missing // (bytes_per_pixel * width)
|
||||
|
||||
return outfile.name, width, height
|
||||
|
||||
|
||||
def process(path, args):
|
||||
m = identify(path)
|
||||
ss = screen_size()
|
||||
@@ -369,7 +273,7 @@ def main(args=sys.argv):
|
||||
detect_support.has_files = args.transfer_mode == 'file'
|
||||
errors = []
|
||||
if args.clear:
|
||||
write_gr_cmd({'a': 'd'})
|
||||
sys.stdout.buffer.write(clear_images_on_screen(delete_data=True))
|
||||
if not items:
|
||||
return
|
||||
if not items:
|
||||
@@ -385,6 +289,10 @@ def main(args=sys.argv):
|
||||
process(item, args)
|
||||
else:
|
||||
process(item, args)
|
||||
except NoImageMagick as e:
|
||||
raise SystemExit(str(e))
|
||||
except ConvertFailed as e:
|
||||
raise SystemExit(str(e))
|
||||
except OpenFailed as e:
|
||||
errors.append(e)
|
||||
if args.place:
|
||||
@@ -396,5 +304,9 @@ def main(args=sys.argv):
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def handle_result(args, current_char, target_window_id, boss):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
0
kittens/resize_window/__init__.py
Normal file
0
kittens/resize_window/__init__.py
Normal file
135
kittens/resize_window/main.py
Normal file
135
kittens/resize_window/main.py
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cmds import cmap, parse_subcommand_cli
|
||||
from kitty.constants import version
|
||||
from kitty.key_encoding import CTRL, ESCAPE, RELEASE, N, S, T, W
|
||||
from kitty.remote_control import encode_send, parse_rc_args
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import styled
|
||||
|
||||
global_opts = None
|
||||
|
||||
|
||||
class Resize(Handler):
|
||||
|
||||
print_on_fail = None
|
||||
|
||||
def __init__(self, opts):
|
||||
self.opts = opts
|
||||
|
||||
def initialize(self):
|
||||
global global_opts
|
||||
global_opts = parse_rc_args(['kitty', '@resize-window'])[0]
|
||||
self.original_size = self.screen_size
|
||||
self.cmd.set_cursor_visible(False)
|
||||
self.cmd.set_line_wrapping(False)
|
||||
self.draw_screen()
|
||||
|
||||
def do_window_resize(self, is_decrease=False, is_horizontal=True, reset=False, multiplier=1):
|
||||
resize_window = cmap['resize-window']
|
||||
increment = self.opts.horizontal_increment if is_horizontal else self.opts.vertical_increment
|
||||
increment *= multiplier
|
||||
if is_decrease:
|
||||
increment *= -1
|
||||
axis = 'reset' if reset else ('horizontal' if is_horizontal else 'vertical')
|
||||
cmdline = [resize_window.name, '--self', '--increment={}'.format(increment), '--axis=' + axis]
|
||||
opts, items = parse_subcommand_cli(resize_window, cmdline)
|
||||
payload = resize_window(global_opts, opts, items)
|
||||
send = {'cmd': resize_window.name, 'version': version, 'payload': payload}
|
||||
self.write(encode_send(send))
|
||||
|
||||
def on_kitty_cmd_response(self, response):
|
||||
if not response.get('ok'):
|
||||
err = response['error']
|
||||
if response.get('tb'):
|
||||
err += '\n' + response['tb']
|
||||
self.print_on_fail = err
|
||||
self.quit_loop(1)
|
||||
return
|
||||
res = response.get('data')
|
||||
if res:
|
||||
self.cmd.bell()
|
||||
|
||||
def on_text(self, text, in_bracketed_paste=False):
|
||||
text = text.upper()
|
||||
if text in 'WNTSR':
|
||||
self.do_window_resize(is_decrease=text in 'NS', is_horizontal=text in 'WN', reset=text == 'R')
|
||||
elif text == 'Q':
|
||||
self.quit_loop(0)
|
||||
|
||||
def on_key(self, key_event):
|
||||
if key_event.type is RELEASE:
|
||||
return
|
||||
if key_event.key is ESCAPE:
|
||||
self.quit_loop(0)
|
||||
elif key_event.key in (W, N, T, S) and key_event.mods & CTRL:
|
||||
self.do_window_resize(is_decrease=key_event.key in (N, S), is_horizontal=key_event.key in (W, N), multiplier=2)
|
||||
|
||||
def on_resize(self, new_size):
|
||||
Handler.on_resize(self, new_size)
|
||||
self.draw_screen()
|
||||
|
||||
def draw_screen(self):
|
||||
self.cmd.clear_screen()
|
||||
print = self.print
|
||||
print(styled('Resize this window', bold=True, fg='gray', fg_intense=True))
|
||||
print()
|
||||
print('Press one of the following keys:')
|
||||
print(' {}ider'.format(styled('W', fg='green')))
|
||||
print(' {}arrower'.format(styled('N', fg='green')))
|
||||
print(' {}aller'.format(styled('T', fg='green')))
|
||||
print(' {}horter'.format(styled('S', fg='green')))
|
||||
print(' {}eset'.format(styled('R', fg='red')))
|
||||
print()
|
||||
print('Press {} to quit resize mode'.format(styled('Esc', italic=True)))
|
||||
print('Hold down {} to double step size'.format(styled('Ctrl', italic=True)))
|
||||
print()
|
||||
print(styled('Sizes', bold=True, fg='white', fg_intense=True))
|
||||
print('Original: {} rows {} cols'.format(self.original_size.rows, self.original_size.cols))
|
||||
print('Current: {} rows {} cols'.format(
|
||||
styled(self.screen_size.rows, fg='magenta'), styled(self.screen_size.cols, fg='magenta')))
|
||||
|
||||
|
||||
OPTIONS = r'''
|
||||
--horizontal-increment
|
||||
default=2
|
||||
type=int
|
||||
The base horizontal increment.
|
||||
|
||||
|
||||
--vertical-increment
|
||||
default=2
|
||||
type=int
|
||||
The base vertical increment.
|
||||
'''.format
|
||||
|
||||
|
||||
def main(args):
|
||||
msg = 'Resize the current window'
|
||||
try:
|
||||
args, items = parse_args(args[1:], OPTIONS, '', msg, 'resize_window')
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0], file=sys.stderr)
|
||||
input('Press Enter to quit')
|
||||
return
|
||||
|
||||
loop = Loop()
|
||||
handler = Resize(args)
|
||||
loop.loop(handler)
|
||||
if handler.print_on_fail:
|
||||
print(handler.print_on_fail)
|
||||
input('Press Enter to quit')
|
||||
raise SystemExit(loop.return_code)
|
||||
|
||||
|
||||
def handle_result(args, data, target_window_id, boss):
|
||||
pass
|
||||
@@ -39,7 +39,15 @@ def create_kitten_handler(kitten, orig_args):
|
||||
from kitty.constants import config_dir
|
||||
kitten = resolved_kitten(kitten)
|
||||
m = import_kitten_main_module(config_dir, kitten)
|
||||
return partial(m['end'], [kitten] + orig_args)
|
||||
ans = partial(m['end'], [kitten] + orig_args)
|
||||
ans.type_of_input = getattr(m['end'], 'type_of_input', None)
|
||||
return ans
|
||||
|
||||
|
||||
def set_debug(kitten):
|
||||
from kittens.tui.loop import debug
|
||||
import builtins
|
||||
builtins.debug = debug
|
||||
|
||||
|
||||
def launch(args):
|
||||
@@ -49,19 +57,32 @@ def launch(args):
|
||||
args = [kitten] + args
|
||||
os.environ['KITTY_CONFIG_DIRECTORY'] = config_dir
|
||||
from kittens.tui.operations import clear_screen, reset_mode
|
||||
set_debug(kitten)
|
||||
m = import_kitten_main_module(config_dir, kitten)
|
||||
result = m['start'](args)
|
||||
try:
|
||||
result = m['start'](args)
|
||||
finally:
|
||||
sys.stdin = sys.__stdin__
|
||||
print(reset_mode('ALTERNATE_SCREEN') + clear_screen(), end='')
|
||||
if result is not None:
|
||||
import json
|
||||
print('OK:', json.dumps(result))
|
||||
data = json.dumps(result)
|
||||
print('OK:', len(data), data)
|
||||
sys.stderr.flush()
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def deserialize(output):
|
||||
import json
|
||||
if output.startswith('OK: '):
|
||||
prefix, sz, rest = output.split(' ', 2)
|
||||
return json.loads(rest[:int(sz)])
|
||||
|
||||
|
||||
def run_kitten(kitten):
|
||||
import runpy
|
||||
kitten = resolved_kitten(kitten)
|
||||
set_debug(kitten)
|
||||
runpy.run_module('kittens.{}.main'.format(kitten), run_name='__main__')
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,39 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from .operations import commander
|
||||
|
||||
|
||||
class Handler:
|
||||
|
||||
def initialize(self, screen_size, quit_loop, wakeup):
|
||||
image_manager_class = None
|
||||
|
||||
def _initialize(self, screen_size, quit_loop, wakeup, start_job, debug, image_manager=None):
|
||||
self.screen_size, self.quit_loop = screen_size, quit_loop
|
||||
self.wakeup = wakeup
|
||||
self.debug = debug
|
||||
self.start_job = start_job
|
||||
self.cmd = commander(self)
|
||||
self.image_manager = image_manager
|
||||
|
||||
def __enter__(self):
|
||||
if self.image_manager is not None:
|
||||
self.image_manager.__enter__()
|
||||
self.debug.fobj = self
|
||||
self.initialize()
|
||||
|
||||
def __exit__(self, *a):
|
||||
del self.write_buf[:]
|
||||
del self.debug.fobj
|
||||
self.finalize()
|
||||
if self.image_manager is not None:
|
||||
self.image_manager.__exit__(*a)
|
||||
|
||||
def initialize(self):
|
||||
pass
|
||||
|
||||
def finalize(self):
|
||||
pass
|
||||
|
||||
def on_resize(self, screen_size):
|
||||
self.screen_size = screen_size
|
||||
@@ -30,6 +58,15 @@ class Handler:
|
||||
def on_eot(self):
|
||||
pass
|
||||
|
||||
def on_wakeup(self):
|
||||
pass
|
||||
|
||||
def on_job_done(self, job_id, job_result):
|
||||
pass
|
||||
|
||||
def on_kitty_cmd_response(self, response):
|
||||
pass
|
||||
|
||||
def write(self, data):
|
||||
if isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
|
||||
256
kittens/tui/images.py
Normal file
256
kittens/tui/images.py
Normal file
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import sys
|
||||
from base64 import standard_b64encode
|
||||
from collections import defaultdict, deque
|
||||
from itertools import count
|
||||
|
||||
from kitty.utils import fit_image, screen_size_function
|
||||
|
||||
from .operations import cursor
|
||||
|
||||
try:
|
||||
fsenc = sys.getfilesystemencoding() or 'utf-8'
|
||||
codecs.lookup(fsenc)
|
||||
except Exception:
|
||||
fsenc = 'utf-8'
|
||||
screen_size = screen_size_function()
|
||||
|
||||
|
||||
class ImageData:
|
||||
|
||||
def __init__(self, fmt, width, height, mode):
|
||||
self.width, self.height, self.fmt, self.mode = width, height, fmt, mode
|
||||
self.transmit_fmt = str(24 if self.mode == 'rgb' else 32)
|
||||
|
||||
|
||||
class OpenFailed(ValueError):
|
||||
|
||||
def __init__(self, path, message):
|
||||
ValueError.__init__(
|
||||
self, 'Failed to open image: {} with error: {}'.format(path, message)
|
||||
)
|
||||
self.path = path
|
||||
|
||||
|
||||
class ConvertFailed(ValueError):
|
||||
|
||||
def __init__(self, path, message):
|
||||
ValueError.__init__(
|
||||
self, 'Failed to convert image: {} with error: {}'.format(path, message)
|
||||
)
|
||||
self.path = path
|
||||
|
||||
|
||||
class NoImageMagick(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def run_imagemagick(path, cmd, keep_stdout=True):
|
||||
import subprocess
|
||||
try:
|
||||
p = subprocess.run(cmd, stdout=subprocess.PIPE if keep_stdout else subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
except FileNotFoundError:
|
||||
raise NoImageMagick('ImageMagick is required to process images')
|
||||
if p.returncode != 0:
|
||||
raise OpenFailed(path, p.stderr.decode('utf-8'))
|
||||
return p
|
||||
|
||||
|
||||
def identify(path):
|
||||
p = run_imagemagick(path, ['identify', '-format', '%m %w %h %A', path])
|
||||
parts = tuple(filter(None, p.stdout.decode('utf-8').split()))
|
||||
mode = 'rgb' if parts[3].lower() == 'false' else 'rgba'
|
||||
return ImageData(parts[0].lower(), int(parts[1]), int(parts[2]), mode)
|
||||
|
||||
|
||||
def convert(path, m, available_width, available_height, scale_up, tdir=None):
|
||||
from tempfile import NamedTemporaryFile
|
||||
width, height = m.width, m.height
|
||||
cmd = ['convert', '-background', 'none', path]
|
||||
if scale_up:
|
||||
if width < available_width:
|
||||
r = available_width / width
|
||||
width, height = available_width, int(height * r)
|
||||
if width > available_width or height > available_height:
|
||||
width, height = fit_image(width, height, available_width, available_height)
|
||||
cmd += ['-resize', '{}x{}'.format(width, height)]
|
||||
with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False, dir=tdir) as outfile:
|
||||
run_imagemagick(path, cmd + [outfile.name])
|
||||
# ImageMagick sometimes generated rgba images smaller than the specified
|
||||
# size. See https://github.com/kovidgoyal/kitty/issues/276 for examples
|
||||
sz = os.path.getsize(outfile.name)
|
||||
bytes_per_pixel = 3 if m.mode == 'rgb' else 4
|
||||
expected_size = bytes_per_pixel * width * height
|
||||
if sz < expected_size:
|
||||
missing = expected_size - sz
|
||||
if missing % (bytes_per_pixel * width) != 0:
|
||||
raise ConvertFailed(path, 'ImageMagick failed to convert {} correctly, it generated {} < {} of data'.format(path, sz, expected_size))
|
||||
height -= missing // (bytes_per_pixel * width)
|
||||
|
||||
return outfile.name, width, height
|
||||
|
||||
|
||||
def can_display_images():
|
||||
import shutil
|
||||
ans = getattr(can_display_images, 'ans', None)
|
||||
if ans is None:
|
||||
ans = shutil.which('convert') is not None
|
||||
can_display_images.ans = ans
|
||||
return ans
|
||||
|
||||
|
||||
class ImageManager:
|
||||
|
||||
def __init__(self, handler):
|
||||
self.image_id_counter = count()
|
||||
self.handler = handler
|
||||
self.filesystem_ok = None
|
||||
self.image_data = {}
|
||||
self.failed_images = {}
|
||||
self.converted_images = {}
|
||||
self.sent_images = {}
|
||||
self.image_id_to_image_data = {}
|
||||
self.image_id_to_converted_data = {}
|
||||
self.transmission_status = {}
|
||||
self.placements_in_flight = defaultdict(deque)
|
||||
|
||||
@property
|
||||
def next_image_id(self):
|
||||
return next(self.image_id_counter) + 2
|
||||
|
||||
def __enter__(self):
|
||||
import tempfile
|
||||
self.tdir = tempfile.mkdtemp(prefix='kitten-images-')
|
||||
with tempfile.NamedTemporaryFile(dir=self.tdir, delete=False) as f:
|
||||
f.write(b'abcd')
|
||||
self.handler.cmd.gr_command(dict(a='q', s=1, v=1, i=1, t='f'), standard_b64encode(f.name.encode(fsenc)))
|
||||
|
||||
def __exit__(self, *a):
|
||||
import shutil
|
||||
shutil.rmtree(self.tdir, ignore_errors=True)
|
||||
self.handler.cmd.clear_images_on_screen(delete_data=True)
|
||||
self.delete_all_sent_images()
|
||||
del self.handler
|
||||
|
||||
def delete_all_sent_images(self):
|
||||
for img_id in self.transmission_status:
|
||||
self.handler.cmd.gr_command({'a': 'd', 'i': img_id})
|
||||
self.transmission_status.clear()
|
||||
|
||||
def handle_response(self, apc):
|
||||
cdata, payload = apc[1:].partition(';')[::2]
|
||||
control = {}
|
||||
for x in cdata.split(','):
|
||||
k, v = x.partition('=')[::2]
|
||||
control[k] = v
|
||||
try:
|
||||
image_id = int(control.get('i', '0'))
|
||||
except Exception:
|
||||
image_id = 0
|
||||
if image_id == 1:
|
||||
self.filesystem_ok = payload == 'OK'
|
||||
return
|
||||
if not image_id:
|
||||
return
|
||||
if not self.transmission_status.get(image_id):
|
||||
self.transmission_status[image_id] = payload
|
||||
else:
|
||||
in_flight = self.placements_in_flight[image_id]
|
||||
if in_flight:
|
||||
pl = in_flight.popleft()
|
||||
if payload.startswith('ENOENT:'):
|
||||
try:
|
||||
self.resend_image(image_id, pl)
|
||||
except Exception:
|
||||
pass
|
||||
if not in_flight:
|
||||
self.placements_in_flight.pop(image_id, None)
|
||||
|
||||
def resend_image(self, image_id, pl):
|
||||
image_data = self.image_id_to_image_data[image_id]
|
||||
skey = self.image_id_to_converted_data[image_id]
|
||||
self.transmit_image(image_data, image_id, *skey)
|
||||
with cursor(self.handler.write):
|
||||
self.handler.cmd.set_cursor_position(pl['x'], pl['y'])
|
||||
self.handler.cmd.gr_command(pl['cmd'])
|
||||
|
||||
def send_image(self, path, max_cols=None, max_rows=None, scale_up=False):
|
||||
path = os.path.abspath(path)
|
||||
if path in self.failed_images:
|
||||
raise self.failed_images[path]
|
||||
if path not in self.image_data:
|
||||
try:
|
||||
self.image_data[path] = identify(path)
|
||||
except Exception as e:
|
||||
self.failed_images[path] = e
|
||||
raise
|
||||
m = self.image_data[path]
|
||||
ss = screen_size()
|
||||
if max_cols is None:
|
||||
max_cols = ss.cols
|
||||
if max_rows is None:
|
||||
max_rows = ss.rows
|
||||
available_width = max_cols * ss.cell_width
|
||||
available_height = max_rows * ss.cell_height
|
||||
key = path, available_width, available_height
|
||||
skey = self.converted_images.get(key)
|
||||
if skey is None:
|
||||
try:
|
||||
self.converted_images[key] = skey = self.convert_image(path, available_width, available_height, m, scale_up)
|
||||
except Exception as e:
|
||||
self.failed_images[path] = e
|
||||
raise
|
||||
final_width, final_height = skey[1:]
|
||||
if final_width == 0:
|
||||
return 0, 0, 0
|
||||
image_id = self.sent_images.get(skey)
|
||||
if image_id is None:
|
||||
image_id = self.next_image_id
|
||||
self.transmit_image(m, image_id, *skey)
|
||||
self.sent_images[skey] = image_id
|
||||
self.image_id_to_converted_data[image_id] = skey
|
||||
self.image_id_to_image_data[image_id] = m
|
||||
return image_id, skey[1], skey[2]
|
||||
|
||||
def hide_image(self, image_id):
|
||||
self.handler.cmd.gr_command({'a': 'd', 'i': image_id})
|
||||
|
||||
def show_image(self, image_id, x, y, src_rect=None):
|
||||
cmd = {'a': 'p', 'i': image_id}
|
||||
if src_rect is not None:
|
||||
cmd['x'], cmd['y'], cmd['w'], cmd['h'] = map(int, src_rect)
|
||||
self.placements_in_flight[image_id].append({'cmd': cmd, 'x': x, 'y': y})
|
||||
with cursor(self.handler.write):
|
||||
self.handler.cmd.set_cursor_position(x, y)
|
||||
self.handler.cmd.gr_command(cmd)
|
||||
|
||||
def convert_image(self, path, available_width, available_height, image_data, scale_up=False):
|
||||
rgba_path, width, height = convert(path, image_data, available_width, available_height, scale_up, tdir=self.tdir)
|
||||
return rgba_path, width, height
|
||||
|
||||
def transmit_image(self, image_data, image_id, rgba_path, width, height):
|
||||
self.transmission_status[image_id] = 0
|
||||
cmd = {'a': 't', 'f': image_data.transmit_fmt, 's': width, 'v': height, 'i': image_id}
|
||||
if self.filesystem_ok:
|
||||
cmd['t'] = 'f'
|
||||
self.handler.cmd.gr_command(
|
||||
cmd, standard_b64encode(rgba_path.encode(fsenc)))
|
||||
else:
|
||||
import zlib
|
||||
data = open(rgba_path, 'rb').read()
|
||||
cmd['S'] = len(data)
|
||||
data = zlib.compress(data)
|
||||
cmd['o'] = 'z'
|
||||
data = standard_b64encode(data)
|
||||
while data:
|
||||
chunk, data = data[:4096], data[4096:]
|
||||
m = 1 if data else 0
|
||||
cmd['m'] = m
|
||||
self.handler.cmd.gr_command(cmd, chunk)
|
||||
cmd.clear()
|
||||
return image_id
|
||||
@@ -15,25 +15,32 @@ import tty
|
||||
from collections import namedtuple
|
||||
from contextlib import closing, contextmanager
|
||||
from functools import partial
|
||||
from queue import Empty, Queue
|
||||
|
||||
from kitty.fast_data_types import parse_input_from_terminal, safe_pipe
|
||||
from kitty.icat import screen_size
|
||||
from kitty.key_encoding import (
|
||||
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, C, D, backspace_key,
|
||||
decode_key_event, enter_key
|
||||
)
|
||||
from kitty.utils import screen_size_function
|
||||
|
||||
from .handler import Handler
|
||||
from .operations import init_state, reset_state, clear_screen
|
||||
from .operations import init_state, reset_state
|
||||
|
||||
screen_size = screen_size_function()
|
||||
|
||||
|
||||
def log(*a, **kw):
|
||||
fd = getattr(log, 'fd', None)
|
||||
if fd is None:
|
||||
fd = log.fd = open('/tmp/kitten-debug', 'w')
|
||||
kw['file'] = fd
|
||||
def debug(*a, **kw):
|
||||
from base64 import standard_b64encode
|
||||
buf = io.StringIO()
|
||||
kw['file'] = buf
|
||||
print(*a, **kw)
|
||||
fd.flush()
|
||||
text = buf.getvalue()
|
||||
text = b'\x1bP@kitty-print|' + standard_b64encode(text.encode('utf-8')) + b'\x1b\\'
|
||||
fobj = getattr(debug, 'fobj', sys.stdout.buffer)
|
||||
fobj.write(text)
|
||||
if hasattr(fobj, 'flush'):
|
||||
fobj.flush()
|
||||
|
||||
|
||||
def write_all(fd, data):
|
||||
@@ -52,6 +59,7 @@ class TermManager:
|
||||
self.input_fd = input_fd
|
||||
self.output_fd = output_fd
|
||||
self.original_fl = fcntl.fcntl(self.input_fd, fcntl.F_GETFL)
|
||||
self.extra_finalize = None
|
||||
self.isatty = os.isatty(self.input_fd)
|
||||
if self.isatty:
|
||||
self.original_termios = termios.tcgetattr(self.input_fd)
|
||||
@@ -66,6 +74,8 @@ class TermManager:
|
||||
if self.isatty:
|
||||
termios.tcsetattr(self.input_fd, termios.TCSADRAIN, self.original_termios)
|
||||
fcntl.fcntl(self.input_fd, fcntl.F_SETFL, self.original_fl)
|
||||
if self.extra_finalize:
|
||||
write_all(self.output_fd, self.extra_finalize)
|
||||
write_all(self.output_fd, reset_state())
|
||||
|
||||
@contextmanager
|
||||
@@ -120,9 +130,11 @@ class UnhandledException(Handler):
|
||||
def __init__(self, tb):
|
||||
self.tb = tb
|
||||
|
||||
def initialize(self, screen_size, quit_loop, wakeup):
|
||||
Handler.initialize(self, screen_size, quit_loop, wakeup)
|
||||
self.write(clear_screen())
|
||||
def initialize(self):
|
||||
self.cmd.clear_screen()
|
||||
self.cmd.set_scrolling_region()
|
||||
self.cmd.set_cursor_visible(True)
|
||||
self.cmd.set_default_colors()
|
||||
self.write(self.tb.replace('\n', '\r\n'))
|
||||
self.write('\r\n')
|
||||
self.write('Press the Enter key to quit')
|
||||
@@ -167,6 +179,24 @@ class Loop:
|
||||
self.sanitize_bracketed_paste = bool(sanitize_bracketed_paste)
|
||||
if self.sanitize_bracketed_paste:
|
||||
self.sanitize_ibp_pat = re.compile(sanitize_bracketed_paste)
|
||||
self.jobs_queue = Queue()
|
||||
|
||||
def start_job(self, job_id, func, *args, **kw):
|
||||
from threading import Thread
|
||||
t = Thread(target=partial(self._run_job, job_id, func), args=args, kwargs=kw, name='LoopJob')
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
def _run_job(self, job_id, func, *args, **kw):
|
||||
try:
|
||||
result = func(*args, **kw)
|
||||
except Exception as err:
|
||||
import traceback
|
||||
entry = {'id': job_id, 'exception': err, 'tb': traceback.format_exc()}
|
||||
else:
|
||||
entry = {'id': job_id, 'result': result}
|
||||
self.jobs_queue.put(entry)
|
||||
self._wakeup_write(b'j')
|
||||
|
||||
def _read_ready(self, handler):
|
||||
if not self.read_allowed:
|
||||
@@ -210,7 +240,9 @@ class Loop:
|
||||
self.handler.on_text(chunk, self.in_bracketed_paste)
|
||||
|
||||
def _on_dcs(self, dcs):
|
||||
pass
|
||||
if dcs.startswith('@kitty-cmd'):
|
||||
import json
|
||||
self.handler.on_kitty_cmd_response(json.loads(dcs[len('@kitty-cmd'):]))
|
||||
|
||||
def _on_csi(self, csi):
|
||||
q = csi[-1]
|
||||
@@ -250,6 +282,9 @@ class Loop:
|
||||
self.handler.on_eot()
|
||||
return
|
||||
self.handler.on_key(k)
|
||||
elif apc.startswith('G'):
|
||||
if self.handler.image_manager is not None:
|
||||
self.handler.image_manager.handle_response(apc)
|
||||
|
||||
def _write_ready(self, handler):
|
||||
if len(handler.write_buf) > self.iov_limit:
|
||||
@@ -283,6 +318,17 @@ class Loop:
|
||||
handler.on_term()
|
||||
if b'i' in data:
|
||||
handler.on_interrupt()
|
||||
if b'j' in data:
|
||||
while True:
|
||||
try:
|
||||
entry = self.jobs_queue.get_nowait()
|
||||
except Empty:
|
||||
break
|
||||
else:
|
||||
job_id = entry.pop('id')
|
||||
handler.on_job_done(job_id, entry)
|
||||
if b'1' in data:
|
||||
handler.on_wakeup()
|
||||
|
||||
def _wakeup_write(self, val):
|
||||
while not os.write(self.wakeup_write_fd, val):
|
||||
@@ -327,31 +373,31 @@ class Loop:
|
||||
signal.signal(signal.SIGINT, self._on_sigint)
|
||||
handler.write_buf = []
|
||||
handler._term_manager = term_manager
|
||||
image_manager = None
|
||||
if handler.image_manager_class is not None:
|
||||
image_manager = handler.image_manager_class(handler)
|
||||
keep_going = True
|
||||
try:
|
||||
handler.initialize(screen_size(), self.quit, self.wakeup)
|
||||
handler._initialize(screen_size(), self.quit, self.wakeup, self.start_job, debug, image_manager)
|
||||
with handler:
|
||||
while keep_going:
|
||||
has_data_to_write = bool(handler.write_buf)
|
||||
if not has_data_to_write and not self.read_allowed:
|
||||
break
|
||||
if has_data_to_write != waiting_for_write:
|
||||
waiting_for_write = has_data_to_write
|
||||
self._modify_output_selector(waiting_for_write)
|
||||
events = select()
|
||||
for key, mask in events:
|
||||
key.data(handler)
|
||||
except Exception:
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
self.return_code = 1
|
||||
keep_going = False
|
||||
while keep_going:
|
||||
has_data_to_write = bool(handler.write_buf)
|
||||
if not has_data_to_write and not self.read_allowed:
|
||||
break
|
||||
if has_data_to_write != waiting_for_write:
|
||||
waiting_for_write = has_data_to_write
|
||||
self._modify_output_selector(waiting_for_write)
|
||||
events = select()
|
||||
for key, mask in events:
|
||||
try:
|
||||
key.data(handler)
|
||||
except Exception:
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
self.return_code = 1
|
||||
keep_going = False
|
||||
break
|
||||
|
||||
term_manager.extra_finalize = b''.join(handler.write_buf).decode('utf-8')
|
||||
|
||||
if tb is not None:
|
||||
self._report_error_loop(tb, term_manager)
|
||||
|
||||
@@ -361,14 +407,15 @@ class Loop:
|
||||
handler = UnhandledException(tb)
|
||||
handler.write_buf = []
|
||||
handler._term_manager = term_manager
|
||||
handler.initialize(screen_size(), self.quit, self.wakeup)
|
||||
while True:
|
||||
has_data_to_write = bool(handler.write_buf)
|
||||
if not has_data_to_write and not self.read_allowed:
|
||||
break
|
||||
if has_data_to_write != waiting_for_write:
|
||||
waiting_for_write = has_data_to_write
|
||||
self._modify_output_selector(waiting_for_write)
|
||||
events = select()
|
||||
for key, mask in events:
|
||||
key.data(handler)
|
||||
handler._initialize(screen_size(), self.quit, self.wakeup, self.start_job, debug)
|
||||
with handler:
|
||||
while True:
|
||||
has_data_to_write = bool(handler.write_buf)
|
||||
if not has_data_to_write and not self.read_allowed:
|
||||
break
|
||||
if has_data_to_write != waiting_for_write:
|
||||
waiting_for_write = has_data_to_write
|
||||
self._modify_output_selector(waiting_for_write)
|
||||
events = select()
|
||||
for key, mask in events:
|
||||
key.data(handler)
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from functools import wraps
|
||||
|
||||
from kitty.terminfo import string_capabilities
|
||||
from kitty.rgb import Color, color_as_sharp, to_color
|
||||
|
||||
S7C1T = '\033 F'
|
||||
SAVE_CURSOR = '\0337'
|
||||
@@ -35,34 +36,69 @@ MODES = dict(
|
||||
)
|
||||
|
||||
|
||||
def set_mode(which, private=True):
|
||||
def set_mode(which, private=True) -> str:
|
||||
num, private = MODES[which]
|
||||
return '\033[{}{}h'.format(private, num)
|
||||
|
||||
|
||||
def reset_mode(which):
|
||||
def reset_mode(which) -> str:
|
||||
num, private = MODES[which]
|
||||
return '\033[{}{}l'.format(private, num)
|
||||
|
||||
|
||||
def clear_screen():
|
||||
return string_capabilities['clear'].replace(r'\E', '\033')
|
||||
def clear_screen() -> str:
|
||||
return '\033[H\033[2J'
|
||||
|
||||
|
||||
def set_window_title(value):
|
||||
def clear_to_eol() -> str:
|
||||
return '\033[K'
|
||||
|
||||
|
||||
def bell() -> str:
|
||||
return '\a'
|
||||
|
||||
|
||||
def beep() -> str:
|
||||
return '\a'
|
||||
|
||||
|
||||
def set_window_title(value) -> str:
|
||||
return ('\033]2;' + value.replace('\033', '').replace('\x9c', '') + '\033\\')
|
||||
|
||||
|
||||
def set_line_wrapping(yes_or_no):
|
||||
def set_line_wrapping(yes_or_no) -> str:
|
||||
return (set_mode if yes_or_no else reset_mode)('DECAWM')
|
||||
|
||||
|
||||
def set_cursor_visible(yes_or_no):
|
||||
def set_cursor_visible(yes_or_no) -> str:
|
||||
return (set_mode if yes_or_no else reset_mode)('DECTCEM')
|
||||
|
||||
|
||||
def set_cursor_position(x, y) -> str: # (0, 0) is top left
|
||||
return '\033[{};{}H'.format(y + 1, x + 1)
|
||||
|
||||
|
||||
def set_scrolling_region(screen_size=None, top=None, bottom=None) -> str:
|
||||
if screen_size is None:
|
||||
return '\033[r'
|
||||
if top is None:
|
||||
top = 0
|
||||
if bottom is None:
|
||||
bottom = screen_size.rows - 1
|
||||
if bottom < 0:
|
||||
bottom = screen_size.rows - 1 + bottom
|
||||
else:
|
||||
bottom += 1
|
||||
return '\033[{};{}r'.format(top + 1, bottom + 1)
|
||||
|
||||
|
||||
def scroll_screen(amt=1) -> str:
|
||||
return '\033[' + str(abs(amt)) + ('T' if amt < 0 else 'S')
|
||||
|
||||
|
||||
STANDARD_COLORS = {name: i for i, name in enumerate(
|
||||
'black red green yellow blue magenta cyan gray'.split())}
|
||||
STANDARD_COLORS['white'] = STANDARD_COLORS['gray']
|
||||
UNDERLINE_STYLES = {name: i + 1 for i, name in enumerate(
|
||||
'straight double curly'.split())}
|
||||
|
||||
@@ -77,20 +113,20 @@ def color_code(color, intense=False, base=30):
|
||||
return e
|
||||
|
||||
|
||||
def sgr(*parts):
|
||||
def sgr(*parts) -> str:
|
||||
return '\033[{}m'.format(';'.join(parts))
|
||||
|
||||
|
||||
def colored(text, color, intense=False, reset_to=None, reset_to_intense=False):
|
||||
def colored(text, color, intense=False, reset_to=None, reset_to_intense=False) -> str:
|
||||
e = color_code(color, intense)
|
||||
return '\033[{}m{}\033[{}m'.format(e, text, 39 if reset_to is None else color_code(reset_to, reset_to_intense))
|
||||
|
||||
|
||||
def faint(text):
|
||||
def faint(text) -> str:
|
||||
return colored(text, 'black', True)
|
||||
|
||||
|
||||
def styled(text, fg=None, bg=None, fg_intense=False, bg_intense=False, italic=None, bold=None, underline=None, underline_color=None, reverse=None):
|
||||
def styled(text, fg=None, bg=None, fg_intense=False, bg_intense=False, italic=None, bold=None, underline=None, underline_color=None, reverse=None) -> str:
|
||||
start, end = [], []
|
||||
if fg is not None:
|
||||
start.append(color_code(fg, fg_intense))
|
||||
@@ -120,6 +156,26 @@ def styled(text, fg=None, bg=None, fg_intense=False, bg_intense=False, italic=No
|
||||
return '\033[{}m{}\033[{}m'.format(';'.join(start), text, ';'.join(end))
|
||||
|
||||
|
||||
def serialize_gr_command(cmd, payload=None):
|
||||
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
|
||||
ans = []
|
||||
w = ans.append
|
||||
w(b'\033_G'), w(cmd.encode('ascii'))
|
||||
if payload:
|
||||
w(b';')
|
||||
w(payload)
|
||||
w(b'\033\\')
|
||||
return b''.join(ans)
|
||||
|
||||
|
||||
def gr_command(cmd, payload=None) -> str:
|
||||
return serialize_gr_command(cmd, payload)
|
||||
|
||||
|
||||
def clear_images_on_screen(delete_data=False) -> str:
|
||||
return serialize_gr_command({'a': 'd', 'd': 'A' if delete_data else 'a'})
|
||||
|
||||
|
||||
def init_state(alternate_screen=True):
|
||||
ans = (
|
||||
S7C1T + SAVE_CURSOR + SAVE_PRIVATE_MODE_VALUES + reset_mode('LNM') +
|
||||
@@ -160,3 +216,33 @@ def alternate_screen(f=None):
|
||||
print(set_mode('ALTERNATE_SCREEN'), end='', file=f)
|
||||
yield
|
||||
print(reset_mode('ALTERNATE_SCREEN'), end='', file=f)
|
||||
|
||||
|
||||
def set_default_colors(fg=None, bg=None) -> str:
|
||||
ans = ''
|
||||
if fg is None:
|
||||
ans += '\x1b]110\x1b\\'
|
||||
else:
|
||||
ans += '\x1b]10;{}\x1b\\'.format(color_as_sharp(fg if isinstance(fg, Color) else to_color(fg)))
|
||||
if bg is None:
|
||||
ans += '\x1b]111\x1b\\'
|
||||
else:
|
||||
ans += '\x1b]11;{}\x1b\\'.format(color_as_sharp(bg if isinstance(bg, Color) else to_color(bg)))
|
||||
return ans
|
||||
|
||||
|
||||
all_cmds = tuple(
|
||||
(name, obj) for name, obj in globals().items()
|
||||
if hasattr(obj, '__annotations__') and obj.__annotations__.get('return') is str)
|
||||
|
||||
|
||||
def writer(handler, func):
|
||||
@wraps(func)
|
||||
def f(self, *a, **kw):
|
||||
handler.write(func(*a, **kw))
|
||||
return f
|
||||
|
||||
|
||||
def commander(handler):
|
||||
ans = {name: writer(handler, obj) for name, obj in all_cmds}
|
||||
return type('CMD', (), ans)()
|
||||
|
||||
@@ -56,7 +56,7 @@ def name(cp):
|
||||
@lru_cache(maxsize=256)
|
||||
def codepoints_matching_search(parts):
|
||||
ans = []
|
||||
if parts and parts[0]:
|
||||
if parts and parts[0] and len(parts[0]) > 1:
|
||||
codepoints = points_for_word(parts[0])
|
||||
for word in parts[1:]:
|
||||
pts = points_for_word(word)
|
||||
@@ -327,8 +327,7 @@ class UnicodeInput(Handler):
|
||||
self.write(set_line_wrapping(False))
|
||||
self.write(set_window_title(_('Unicode input')))
|
||||
|
||||
def initialize(self, *args):
|
||||
Handler.initialize(self, *args)
|
||||
def initialize(self):
|
||||
self.init_terminal_state()
|
||||
self.draw_screen()
|
||||
|
||||
|
||||
64558
kittens/unicode_input/names.h
generated
64558
kittens/unicode_input/names.h
generated
File diff suppressed because one or more lines are too long
@@ -9,32 +9,61 @@
|
||||
|
||||
static PyObject*
|
||||
all_words(PYNOARG) {
|
||||
PyObject *ans = PyTuple_New(arraysz(idx_to_word));
|
||||
PyObject *ans = PyTuple_New(arraysz(all_words_map));
|
||||
if (!ans) return NULL;
|
||||
for (size_t i = 0; i < arraysz(idx_to_word); i++) {
|
||||
PyObject *w = PyUnicode_FromString(idx_to_word[i]);
|
||||
for (size_t i = 0; i < arraysz(all_words_map); i++) {
|
||||
PyObject *w = PyUnicode_FromString(all_words_map[i]);
|
||||
if (w == NULL) { Py_DECREF(ans); return NULL; }
|
||||
PyTuple_SET_ITEM(ans, i, w);
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static inline void
|
||||
add_matches(const word_trie *wt, char_type *codepoints, size_t *pos, const size_t sz) {
|
||||
size_t num = mark_groups[wt->match_offset];
|
||||
for (size_t i = wt->match_offset + 1; i < wt->match_offset + 1 + num && *pos < sz; i++, (*pos)++) {
|
||||
codepoints[*pos] = mark_to_cp[mark_groups[i]];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
process_trie_node(const word_trie *wt, char_type *codepoints, size_t *pos, const size_t sz) {
|
||||
if (wt->match_offset) add_matches(wt, codepoints, pos, sz);
|
||||
size_t num_children = children_array[wt->children_offset];
|
||||
if (!num_children) return;
|
||||
for (size_t c = wt->children_offset + 1; c < wt->children_offset + 1 + num_children; c++) {
|
||||
if (*pos > sz) return;
|
||||
uint32_t x = children_array[c];
|
||||
process_trie_node(&all_trie_nodes[x >> 8], codepoints, pos, sz);
|
||||
}
|
||||
}
|
||||
|
||||
static inline PyObject*
|
||||
codepoints_for_word(const char *word, size_t len) {
|
||||
PyObject *ans = PyFrozenSet_New(NULL); if (ans == NULL) return NULL;
|
||||
const unsigned short *words = words_for_first_letter[(unsigned)*word];
|
||||
if (words == NULL) return ans;
|
||||
for (unsigned short i = 1; i <= words[0]; i++) {
|
||||
unsigned short word_idx = words[i];
|
||||
const char *w = idx_to_word[word_idx];
|
||||
if (strncmp(word, w, len) == 0 && strlen(w) == len) {
|
||||
const char_type* codepoints = codepoints_for_word_idx[word_idx];
|
||||
for (char_type i = 1; i <= codepoints[0]; i++) {
|
||||
PyObject *t = PyLong_FromUnsignedLong(codepoints[i]); if (t == NULL) { Py_DECREF(ans); return NULL; }
|
||||
int ret = PySet_Add(ans, t); Py_DECREF(t); if (ret != 0) { Py_DECREF(ans); return NULL; }
|
||||
const word_trie *wt = all_trie_nodes;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char ch = word[i];
|
||||
size_t num_children = children_array[wt->children_offset];
|
||||
if (!num_children) return PyFrozenSet_New(NULL);
|
||||
bool found = false;
|
||||
for (size_t c = wt->children_offset + 1; c < wt->children_offset + 1 + num_children; c++) {
|
||||
uint32_t x = children_array[c];
|
||||
if ((x & 0xff) == ch) {
|
||||
found = true;
|
||||
wt = &all_trie_nodes[x >> 8];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!found) return PyFrozenSet_New(NULL);
|
||||
}
|
||||
static char_type codepoints[1024];
|
||||
size_t cpos = 0;
|
||||
process_trie_node(wt, codepoints, &cpos, arraysz(codepoints));
|
||||
PyObject *ans = PyFrozenSet_New(NULL); if (ans == NULL) return NULL;
|
||||
for (size_t i = 0; i < cpos; i++) {
|
||||
PyObject *t = PyLong_FromUnsignedLong(codepoints[i]); if (t == NULL) { Py_DECREF(ans); return NULL; }
|
||||
int ret = PySet_Add(ans, t); Py_DECREF(t); if (ret != 0) { Py_DECREF(ans); return NULL; }
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
@@ -56,12 +85,13 @@ nfc(PyObject *self UNUSED, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
METHODB(all_words, METH_NOARGS),
|
||||
{"all_words", (PyCFunction)all_words, METH_NOARGS, ""},
|
||||
{"codepoints_for_word", (PyCFunction)cfw, METH_VARARGS, ""},
|
||||
{"name_for_codepoint", (PyCFunction)nfc, METH_VARARGS, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
#if PY_VERSION_HEX >= 0x03000000
|
||||
static struct PyModuleDef module = {
|
||||
.m_base = PyModuleDef_HEAD_INIT,
|
||||
.m_name = "unicode_names", /* name of module */
|
||||
@@ -79,3 +109,13 @@ PyInit_unicode_names(void) {
|
||||
if (m == NULL) return NULL;
|
||||
return m;
|
||||
}
|
||||
#else
|
||||
EXPORTED
|
||||
initunicode_names(void) {
|
||||
PyObject *m;
|
||||
m = Py_InitModule3("unicode_names", module_methods,
|
||||
""
|
||||
);
|
||||
if (m == NULL) return;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3,6 +3,7 @@ uniform uvec2 viewport;
|
||||
uniform vec3 default_bg;
|
||||
uniform vec3 active_border_color;
|
||||
uniform vec3 inactive_border_color;
|
||||
uniform vec3 bell_border_color;
|
||||
in uvec4 rect; // left, top, right, bottom
|
||||
in uint rect_color;
|
||||
out vec3 color;
|
||||
@@ -37,5 +38,5 @@ void main() {
|
||||
gl_Position = vec4(to_opengl(rect[pos.x], rect[pos.y]), 0, 1);
|
||||
int rc = int(rect_color);
|
||||
vec3 window_bg = vec3(to_color(rect_color >> 24), to_color(rect_color >> 16), to_color(rect_color >> 8));
|
||||
color = float(1 & rc) * default_bg + float((2 & rc) >> 1) * active_border_color + float((4 & rc) >> 2) * inactive_border_color + float((8 & rc) >> 3) * window_bg;
|
||||
color = float(1 & rc) * default_bg + float((2 & rc) >> 1) * active_border_color + float((4 & rc) >> 2) * inactive_border_color + float((8 & rc) >> 3) * window_bg + float((16 & rc) >> 4) * bell_border_color;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ class Borders:
|
||||
g = w.geometry
|
||||
if bw > 0 and draw_window_borders:
|
||||
# Draw the border rectangles
|
||||
color = 2 if w is active_window else 4
|
||||
color = 2 if w is active_window else (16 if w.needs_attention else 4)
|
||||
border(
|
||||
self.os_window_id, self.tab_id,
|
||||
color, bw, g.left - fw, g.top - fw, g.right + fw,
|
||||
|
||||
104
kitty/boss.py
104
kitty/boss.py
@@ -6,7 +6,6 @@ import atexit
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
from functools import partial
|
||||
from gettext import gettext as _
|
||||
from weakref import WeakValueDictionary
|
||||
@@ -15,13 +14,16 @@ from .cli import create_opts, parse_args
|
||||
from .config import (
|
||||
MINIMUM_FONT_SIZE, initial_window_size, prepare_config_file_for_editing
|
||||
)
|
||||
from .constants import appname, editor, set_boss, config_dir
|
||||
from .config_utils import to_cmdline
|
||||
from .constants import (
|
||||
appname, config_dir, editor, set_boss, supports_primary_selection
|
||||
)
|
||||
from .fast_data_types import (
|
||||
ChildMonitor, create_os_window, current_os_window, destroy_global_data,
|
||||
destroy_sprite_map, get_clipboard_string, glfw_post_empty_event,
|
||||
layout_sprite_map, mark_os_window_for_close, set_clipboard_string,
|
||||
set_dpi_from_os_window, set_in_sequence_mode, show_window,
|
||||
toggle_fullscreen, viewport_for_window
|
||||
destroy_sprite_map, get_clipboard_string,
|
||||
glfw_post_empty_event, layout_sprite_map, mark_os_window_for_close,
|
||||
set_clipboard_string, set_dpi_from_os_window, set_in_sequence_mode,
|
||||
show_window, toggle_fullscreen, viewport_for_window
|
||||
)
|
||||
from .fonts.render import prerender, resize_fonts, set_font_family
|
||||
from .keys import get_shortcut, shortcut_matches
|
||||
@@ -42,6 +44,7 @@ def initialize_renderer():
|
||||
|
||||
|
||||
def listen_on(spec):
|
||||
import socket
|
||||
family, address, socket_path = parse_address_spec(spec)
|
||||
s = socket.socket(family)
|
||||
atexit.register(remove_socket_file, s, socket_path)
|
||||
@@ -148,10 +151,20 @@ class Boss:
|
||||
field, exp = match.split(':', 1)
|
||||
except ValueError:
|
||||
return
|
||||
pat = re.compile(exp)
|
||||
for window in self.all_windows:
|
||||
if window.matches(field, pat):
|
||||
yield window
|
||||
if field == 'num':
|
||||
tab = self.active_tab
|
||||
if tab is not None:
|
||||
try:
|
||||
w = tab.get_nth_window(int(exp))
|
||||
except Exception:
|
||||
return
|
||||
if w is not None:
|
||||
yield w
|
||||
else:
|
||||
pat = re.compile(exp)
|
||||
for window in self.all_windows:
|
||||
if window.matches(field, pat):
|
||||
yield window
|
||||
|
||||
def tab_for_window(self, window):
|
||||
for tab in self.all_tabs:
|
||||
@@ -400,6 +413,25 @@ class Boss:
|
||||
if matched_action is not None:
|
||||
self.dispatch_action(matched_action)
|
||||
|
||||
def start_resizing_window(self):
|
||||
w = self.active_window
|
||||
if w is None:
|
||||
return
|
||||
overlay_window = self._run_kitten('resize_window', args=[
|
||||
'--horizontal-increment={}'.format(self.opts.window_resize_step_cells),
|
||||
'--vertical-increment={}'.format(self.opts.window_resize_step_lines)
|
||||
])
|
||||
if overlay_window is not None:
|
||||
overlay_window.allow_remote_control = True
|
||||
|
||||
def resize_layout_window(self, window, increment, is_horizontal, reset=False):
|
||||
tab = window.tabref()
|
||||
if tab is None or not increment:
|
||||
return False
|
||||
if reset:
|
||||
return tab.reset_window_sizes()
|
||||
return tab.resize_window_by(window.id, increment, is_horizontal)
|
||||
|
||||
def default_bg_changed_for(self, window_id):
|
||||
w = self.window_id_map.get(window_id)
|
||||
if w is not None:
|
||||
@@ -440,6 +472,12 @@ class Boss:
|
||||
w = tm.active_window
|
||||
if w is not None:
|
||||
w.focus_changed(focused)
|
||||
tm.mark_tab_bar_dirty()
|
||||
|
||||
def update_tab_bar_data(self, os_window_id):
|
||||
tm = self.os_window_map.get(os_window_id)
|
||||
if tm is not None:
|
||||
tm.update_tab_bar_data()
|
||||
|
||||
def on_drop(self, os_window_id, paths):
|
||||
tm = self.os_window_map.get(os_window_id)
|
||||
@@ -479,39 +517,51 @@ class Boss:
|
||||
output += str(s.linebuf.line(i))
|
||||
return output
|
||||
|
||||
def _run_kitten(self, kitten, args=(), type_of_input='none'):
|
||||
def _run_kitten(self, kitten, args=()):
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
if w is not None and tab is not None and w.overlay_for is None:
|
||||
orig_args, args = list(args), list(args)
|
||||
from kittens.runner import create_kitten_handler
|
||||
end_kitten = create_kitten_handler(kitten, orig_args)
|
||||
args[0:0] = [config_dir, kitten]
|
||||
if type_of_input in ('text', 'history', 'ansi', 'ansi-history'):
|
||||
data = w.as_text(as_ansi='ansi' in type_of_input, add_history='history' in type_of_input).encode('utf-8')
|
||||
elif type_of_input == 'none':
|
||||
type_of_input = end_kitten.type_of_input
|
||||
if type_of_input in ('text', 'history', 'ansi', 'ansi-history', 'screen', 'screen-history', 'screen-ansi', 'screen-ansi-history'):
|
||||
data = w.as_text(
|
||||
as_ansi='ansi' in type_of_input,
|
||||
add_history='history' in type_of_input,
|
||||
add_wrap_markers='screen' in type_of_input
|
||||
).encode('utf-8')
|
||||
elif type_of_input is None:
|
||||
data = None
|
||||
else:
|
||||
raise ValueError('Unknown type_of_input: {}'.format(type_of_input))
|
||||
from kittens.runner import create_kitten_handler
|
||||
end_kitten = create_kitten_handler(kitten, orig_args)
|
||||
copts = {k: self.opts[k] for k in ('select_by_word_characters', 'open_url_with')}
|
||||
overlay_window = tab.new_special_window(
|
||||
SpecialWindow(
|
||||
['kitty', '+runpy', 'from kittens.runner import main; main()'] + args,
|
||||
stdin=data,
|
||||
env={'KITTY_COMMON_OPTS': json.dumps(copts)},
|
||||
env={
|
||||
'KITTY_COMMON_OPTS': json.dumps(copts),
|
||||
'PYTHONWARNINGS': 'ignore',
|
||||
'OVERLAID_WINDOW_LINES': str(w.screen.lines),
|
||||
'OVERLAID_WINDOW_COLS': str(w.screen.columns),
|
||||
},
|
||||
overlay_for=w.id))
|
||||
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
|
||||
return overlay_window
|
||||
|
||||
def run_kitten(self, type_of_input, kitten, *args):
|
||||
def kitten(self, kitten, *args):
|
||||
import shlex
|
||||
cmdline = args[0] if args else ''
|
||||
args = shlex.split(cmdline) if cmdline else []
|
||||
self._run_kitten(kitten, args, type_of_input)
|
||||
self._run_kitten(kitten, args)
|
||||
|
||||
def on_kitten_finish(self, target_window_id, end_kitten, source_window):
|
||||
output = self.get_output(source_window, num_lines=None)
|
||||
if output.startswith('OK: '):
|
||||
data = json.loads(output.partition(' ')[2].strip())
|
||||
from kittens.runner import deserialize
|
||||
data = deserialize(output)
|
||||
if data is not None:
|
||||
end_kitten(data, target_window_id, self)
|
||||
|
||||
def input_unicode_character(self):
|
||||
@@ -554,14 +604,12 @@ class Boss:
|
||||
def switch_focus_to(self, window_idx):
|
||||
tab = self.active_tab
|
||||
tab.set_active_window_idx(window_idx)
|
||||
old_focus = tab.active_window
|
||||
if not old_focus.destroyed:
|
||||
old_focus.focus_changed(False)
|
||||
tab.active_window.focus_changed(True)
|
||||
|
||||
def open_url(self, url, program=None):
|
||||
def open_url(self, url, program=None, cwd=None):
|
||||
if url:
|
||||
open_url(url, program or self.opts.open_url_with)
|
||||
if isinstance(program, str):
|
||||
program = to_cmdline(program)
|
||||
open_url(url, program or self.opts.open_url_with, cwd=cwd)
|
||||
|
||||
def open_url_lines(self, lines, program=None):
|
||||
self.open_url(''.join(lines), program)
|
||||
@@ -587,7 +635,7 @@ class Boss:
|
||||
self.paste_to_active_window(text)
|
||||
|
||||
def paste_from_selection(self):
|
||||
text = get_primary_selection()
|
||||
text = get_primary_selection() if supports_primary_selection else get_clipboard_string()
|
||||
self.paste_to_active_window(text)
|
||||
|
||||
def set_primary_selection(self):
|
||||
|
||||
@@ -19,7 +19,7 @@ in float bg_alpha;
|
||||
|
||||
#ifdef NEEDS_FOREGROUND
|
||||
uniform sampler2DArray sprites;
|
||||
uniform float inactive_text_alpha;
|
||||
in float effective_text_alpha;
|
||||
in vec3 sprite_pos;
|
||||
in vec3 underline_pos;
|
||||
in vec3 strike_pos;
|
||||
@@ -61,7 +61,7 @@ vec4 calculate_foreground() {
|
||||
// Since strike and text are the same color, we simply add the alpha values
|
||||
float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
|
||||
// Underline color might be different, so alpha blend
|
||||
return alpha_blend(decoration_fg, underline_alpha * inactive_text_alpha, fg, combined_alpha * inactive_text_alpha);
|
||||
return alpha_blend(decoration_fg, underline_alpha * effective_text_alpha, fg, combined_alpha * effective_text_alpha);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#version GLSL_VERSION
|
||||
#define WHICH_PROGRAM
|
||||
#define NOT_TRANSPARENT
|
||||
#define SHIFTS
|
||||
|
||||
// Inputs {{{
|
||||
layout(std140) uniform CellRenderData {
|
||||
@@ -45,12 +46,15 @@ out float bg_alpha;
|
||||
#endif
|
||||
|
||||
#ifdef NEEDS_FOREGROUND
|
||||
uniform float inactive_text_alpha;
|
||||
uniform float dim_opacity;
|
||||
out vec3 sprite_pos;
|
||||
out vec3 underline_pos;
|
||||
out vec3 strike_pos;
|
||||
out vec3 foreground;
|
||||
out vec3 decoration_fg;
|
||||
out float colored_sprite;
|
||||
out float effective_text_alpha;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -63,9 +67,6 @@ const uint ONE = uint(1);
|
||||
const uint TWO = uint(2);
|
||||
const uint THREE = uint(3);
|
||||
const uint FOUR = uint(4);
|
||||
const uint DECORATION_MASK = uint(3);
|
||||
const uint STRIKE_MASK = uint(1);
|
||||
const uint REVERSE_MASK = uint(1);
|
||||
|
||||
vec3 color_to_vec(uint c) {
|
||||
uint r, g, b;
|
||||
@@ -134,7 +135,7 @@ void main() {
|
||||
// set cell color indices {{{
|
||||
uvec2 default_colors = uvec2(default_fg, default_bg);
|
||||
uint text_attrs = sprite_coords[3];
|
||||
uint is_inverted = ((text_attrs >> 6) & REVERSE_MASK) + inverted;
|
||||
uint is_inverted = ((text_attrs >> REVERSE_SHIFT) & ONE) + inverted;
|
||||
int fg_index = fg_index_map[is_inverted];
|
||||
int bg_index = 1 - fg_index;
|
||||
float cursor = is_cursor(c, r);
|
||||
@@ -151,13 +152,15 @@ void main() {
|
||||
// Foreground
|
||||
uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]);
|
||||
foreground = color_to_vec(resolved_fg);
|
||||
float has_dim = float((text_attrs >> DIM_SHIFT) & ONE);
|
||||
effective_text_alpha = inactive_text_alpha * mix(1.0, dim_opacity, has_dim);
|
||||
// Selection
|
||||
foreground = choose_color(float(is_selected & ONE), color_to_vec(highlight_fg), foreground);
|
||||
// Underline and strike through (rendered via sprites)
|
||||
float in_url = float((is_selected & TWO) >> 1);
|
||||
decoration_fg = choose_color(in_url, color_to_vec(url_color), to_color(colors[2], resolved_fg));
|
||||
underline_pos = choose_color(in_url, to_sprite_pos(pos, url_style, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> 2) & DECORATION_MASK, ZERO, ZERO));
|
||||
strike_pos = to_sprite_pos(pos, ((text_attrs >> 7) & STRIKE_MASK) * FOUR, ZERO, ZERO);
|
||||
underline_pos = choose_color(in_url, to_sprite_pos(pos, url_style, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> DECORATION_SHIFT) & THREE, ZERO, ZERO));
|
||||
strike_pos = to_sprite_pos(pos, ((text_attrs >> STRIKE_SHIFT) & ONE) * FOUR, ZERO, ZERO);
|
||||
|
||||
// Cursor
|
||||
foreground = choose_color(cursor, bg, foreground);
|
||||
|
||||
@@ -502,7 +502,7 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, double now, OSWindow *os_w
|
||||
ans->is_visible = false;
|
||||
if (rd->screen->scrolled_by || !screen_is_cursor_visible(rd->screen)) return;
|
||||
double time_since_start_blink = now - os_window->cursor_blink_zero_time;
|
||||
bool cursor_blinking = OPT(cursor_blink_interval) > 0 && os_window->is_focused && time_since_start_blink <= OPT(cursor_stop_blinking_after) ? true : false;
|
||||
bool cursor_blinking = OPT(cursor_blink_interval) > 0 && os_window->is_focused && (OPT(cursor_stop_blinking_after) == 0 || time_since_start_blink <= OPT(cursor_stop_blinking_after));
|
||||
bool do_draw_cursor = true;
|
||||
if (cursor_blinking) {
|
||||
int t = (int)(time_since_start_blink * 1000);
|
||||
@@ -572,6 +572,10 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
|
||||
bool needs_render = os_window->needs_render;
|
||||
os_window->needs_render = false;
|
||||
if (TD.screen && os_window->num_tabs > 1) {
|
||||
if (!os_window->tab_bar_data_updated) {
|
||||
call_boss(update_tab_bar_data, "K", os_window->id);
|
||||
os_window->tab_bar_data_updated = true;
|
||||
}
|
||||
if (send_cell_data_to_gpu(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true;
|
||||
}
|
||||
if (OPT(mouse_hide_wait) > 0 && now - os_window->last_mouse_activity_at > OPT(mouse_hide_wait)) hide_mouse(os_window);
|
||||
|
||||
173
kitty/cli.py
173
kitty/cli.py
@@ -4,13 +4,32 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import deque
|
||||
|
||||
from .config import defaults, load_config
|
||||
from .config_utils import resolve_config
|
||||
from .constants import appname, defconf, is_macos, is_wayland, str_version
|
||||
from .layout import all_layouts
|
||||
|
||||
CONFIG_HELP = '''\
|
||||
Specify a path to the configuration file(s) to use. All configuration files are
|
||||
merged onto the builtin {conf_name}.conf, overriding the builtin values. This option
|
||||
can be specified multiple times to read multiple configuration files in
|
||||
sequence, which are merged. Use the special value NONE to not load a config
|
||||
file.
|
||||
|
||||
If this option is not specified, config files are searched for in the order:
|
||||
"$XDG_CONFIG_HOME/{appname}/{conf_name}.conf", "~/.config/{appname}/{conf_name}.conf", {macos_confpath}
|
||||
"$XDG_CONFIG_DIRS/{appname}/{conf_name}.conf". The first one that exists is used as the
|
||||
config file.
|
||||
|
||||
If the environment variable "KITTY_CONFIG_DIRECTORY" is specified, that
|
||||
directory is always used and the above searching does not happen.
|
||||
|
||||
If "/etc/xdg/{appname}/{conf_name}.conf" exists it is merged before (i.e. with lower
|
||||
priority) than any user config files. It can be used to specify system-wide
|
||||
defaults for all users.
|
||||
'''.replace('{macos_confpath}', '~/Library/Preferences/{appname}/{conf_name}.conf, ' if is_macos else '')
|
||||
|
||||
OPTIONS = '''
|
||||
--class
|
||||
@@ -30,26 +49,9 @@ Set the window title. This will override any title set by the program running in
|
||||
only use this if you are running a program that does not set titles.
|
||||
|
||||
|
||||
--config
|
||||
--config -c
|
||||
type=list
|
||||
Specify a path to the configuration file(s) to use. All configuration files are
|
||||
merged onto the builtin kitty.conf, overriding the builtin values. This option
|
||||
can be specified multiple times to read multiple configuration files in
|
||||
sequence, which are merged. Use the special value NONE to not load a config
|
||||
file.
|
||||
|
||||
If this option is not specified, config files are searched for in the order:
|
||||
"$XDG_CONFIG_HOME/kitty/kitty.conf", "~/.config/kitty/kitty.conf", {macos_confpath}
|
||||
"$XDG_CONFIG_DIRS/kitty/kitty.conf". The first one that exists is used as the
|
||||
config file.
|
||||
|
||||
If the environment variable "KITTY_CONFIG_DIRECTORY" is specified, that
|
||||
directory is always used and the above searching does not happen.
|
||||
|
||||
If "/etc/xdg/kitty/kitty.conf" exists it is merged before (i.e. with lower
|
||||
priority) than any user config files. It can be used to specify system-wide
|
||||
defaults for all users.
|
||||
|
||||
{config_help}
|
||||
|
||||
--override -o
|
||||
type=list
|
||||
@@ -57,10 +59,6 @@ Override individual configuration options, can be specified multiple times.
|
||||
Syntax: |_ name=value|. For example: |_ -o font_size=20|
|
||||
|
||||
|
||||
--cmd -c
|
||||
Run python code in the kitty context
|
||||
|
||||
|
||||
--directory -d
|
||||
default=.
|
||||
Change to the specified directory when launching
|
||||
@@ -72,12 +70,6 @@ condition=not is_macos
|
||||
Detach from the controlling terminal, if any
|
||||
|
||||
|
||||
--window-layout
|
||||
type=choices
|
||||
choices={window_layout_choices}
|
||||
The window layout to use on startup.
|
||||
|
||||
|
||||
--session
|
||||
Path to a file containing the startup |_ session| (tabs, windows, layout, programs).
|
||||
See the README file for details and an example.
|
||||
@@ -120,8 +112,10 @@ Output commands received from child process to stdout
|
||||
|
||||
|
||||
--replay-commands
|
||||
type=bool-set
|
||||
Replay previously dumped commands
|
||||
Replay previously dumped commands. Specify the path to a dump file previously created by --dump-commands. You
|
||||
can open a new kitty window to replay the commands with:
|
||||
|
||||
kitty sh -c "kitty --replay-commands /path/to/dump/file; read"
|
||||
|
||||
|
||||
--dump-bytes
|
||||
@@ -141,6 +135,11 @@ Print out information about the selection of fallback fonts for characters not p
|
||||
--debug-config
|
||||
type=bool-set
|
||||
Print out information about the system and kitty configuration.
|
||||
|
||||
|
||||
--execute -e
|
||||
type=bool-set
|
||||
!
|
||||
'''
|
||||
|
||||
|
||||
@@ -289,8 +288,8 @@ def wrap(text, limit=80):
|
||||
|
||||
|
||||
def print_help_for_seq(seq, usage, message, appname):
|
||||
from kitty.icat import screen_size
|
||||
screen_size.changed = True
|
||||
from kitty.utils import screen_size_function
|
||||
screen_size = screen_size_function()
|
||||
try:
|
||||
linesz = min(screen_size().cols, 76)
|
||||
except EnvironmentError:
|
||||
@@ -326,12 +325,15 @@ def print_help_for_seq(seq, usage, message, appname):
|
||||
if isinstance(opt, str):
|
||||
a('{}:'.format(title(opt)))
|
||||
continue
|
||||
help_text = opt['help']
|
||||
if help_text == '!':
|
||||
continue # hidden option
|
||||
a(' ' + ', '.join(map(green, sorted(opt['aliases']))))
|
||||
if not opt.get('type', '').startswith('bool-'):
|
||||
blocks[-1] += '={}'.format(italic(opt['dest'].upper()))
|
||||
if opt.get('help'):
|
||||
defval = opt.get('default')
|
||||
t = opt['help'].replace('%default', str(defval))
|
||||
t = help_text.replace('%default', str(defval))
|
||||
wa(prettify(t.strip()), indent=4)
|
||||
if defval is not None:
|
||||
wa('Default: {}'.format(defval), indent=4)
|
||||
@@ -341,6 +343,7 @@ def print_help_for_seq(seq, usage, message, appname):
|
||||
|
||||
text = '\n'.join(blocks) + '\n\n' + version()
|
||||
if print_help_for_seq.allow_pager and sys.stdout.isatty():
|
||||
import subprocess
|
||||
p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE)
|
||||
p.communicate(text.encode('utf-8'))
|
||||
raise SystemExit(p.wait())
|
||||
@@ -480,8 +483,8 @@ def parse_cmdline(oc, disabled, args=None):
|
||||
def options_spec():
|
||||
if not hasattr(options_spec, 'ans'):
|
||||
options_spec.ans = OPTIONS.format(
|
||||
appname=appname, macos_confpath='~/Library/Preferences/kitty/kitty.conf, ' if is_macos else '',
|
||||
window_layout_choices=', '.join(all_layouts)
|
||||
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname)
|
||||
|
||||
)
|
||||
return options_spec.ans
|
||||
|
||||
@@ -496,70 +499,82 @@ def parse_args(args=None, ospec=options_spec, usage=None, message=None, appname=
|
||||
SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf'
|
||||
|
||||
|
||||
def resolve_config(config_files_on_cmd_line):
|
||||
if config_files_on_cmd_line:
|
||||
if 'NONE' not in config_files_on_cmd_line:
|
||||
yield SYSTEM_CONF
|
||||
for cf in config_files_on_cmd_line:
|
||||
yield cf
|
||||
else:
|
||||
yield SYSTEM_CONF
|
||||
yield defconf
|
||||
|
||||
|
||||
def print_shortcut(key, action):
|
||||
def print_shortcut(key_sequence, action):
|
||||
if not getattr(print_shortcut, 'maps', None):
|
||||
from kitty.keys import defines
|
||||
v = vars(defines)
|
||||
mmap = {m.split('_')[-1].lower(): x for m, x in v.items() if m.startswith('GLFW_MOD_')}
|
||||
kmap = {k.split('_')[-1].lower(): x for k, x in v.items() if k.startswith('GLFW_KEY_')}
|
||||
mmap = {m[len('GLFW_MOD_'):].lower(): x for m, x in v.items() if m.startswith('GLFW_MOD_')}
|
||||
kmap = {k[len('GLFW_KEY_'):].lower(): x for k, x in v.items() if k.startswith('GLFW_KEY_')}
|
||||
krmap = {v: k for k, v in kmap.items()}
|
||||
print_shortcut.maps = mmap, krmap
|
||||
mmap, krmap = print_shortcut.maps
|
||||
names = []
|
||||
mods, key = key
|
||||
for name, val in mmap.items():
|
||||
if mods & val:
|
||||
names.append(name)
|
||||
if key:
|
||||
names.append(krmap[key])
|
||||
print('\t', '+'.join(names), action)
|
||||
keys = []
|
||||
for key in key_sequence:
|
||||
names = []
|
||||
mods, key = key
|
||||
for name, val in mmap.items():
|
||||
if mods & val:
|
||||
names.append(name)
|
||||
if key:
|
||||
names.append(krmap[key])
|
||||
keys.append('+'.join(names))
|
||||
|
||||
print('\t', ' > '.join(keys), action)
|
||||
|
||||
|
||||
def print_shortcut_changes(defns, text, changes):
|
||||
if changes:
|
||||
print(title(text))
|
||||
|
||||
for k in sorted(changes):
|
||||
print_shortcut(k, defns[k])
|
||||
|
||||
|
||||
def compare_keymaps(final, initial):
|
||||
added = set(final) - set(initial)
|
||||
removed = set(initial) - set(final)
|
||||
changed = {k for k in set(final) & set(initial) if final[k] != initial[k]}
|
||||
if added:
|
||||
print(title('Added shortcuts:'))
|
||||
for k in added:
|
||||
print_shortcut(k, final[k])
|
||||
if removed:
|
||||
print(title('Removed shortcuts:'))
|
||||
for k in removed:
|
||||
print_shortcut(k, initial[k])
|
||||
if changed:
|
||||
print(title('Changed shortcuts:'))
|
||||
for k in changed:
|
||||
print_shortcut(k, final[k])
|
||||
print_shortcut_changes(final, 'Added shortcuts:', added)
|
||||
print_shortcut_changes(initial, 'Removed shortcuts:', removed)
|
||||
print_shortcut_changes(final, 'Changed shortcuts:', changed)
|
||||
|
||||
|
||||
def flatten_sequence_map(m):
|
||||
ans = {}
|
||||
for k, rest_map in m.items():
|
||||
for r, action in rest_map.items():
|
||||
ans[(k,) + (r)] = action
|
||||
return ans
|
||||
|
||||
|
||||
def compare_opts(opts):
|
||||
print('\nConfig options different from defaults:')
|
||||
for f in sorted(defaults._fields):
|
||||
if getattr(opts, f) != getattr(defaults, f):
|
||||
if f == 'keymap':
|
||||
compare_keymaps(opts.keymap, defaults.keymap)
|
||||
else:
|
||||
print(title('{:20s}'.format(f)), getattr(opts, f))
|
||||
default_opts = load_config()
|
||||
changed_opts = [
|
||||
f for f in sorted(defaults._fields)
|
||||
if f not in ('key_definitions', 'keymap', 'sequence_map') and getattr(opts, f) != getattr(defaults, f)
|
||||
]
|
||||
field_len = max(map(len, changed_opts)) if changed_opts else 20
|
||||
fmt = '{{:{:d}s}}'.format(field_len)
|
||||
for f in changed_opts:
|
||||
print(title(fmt.format(f)), getattr(opts, f))
|
||||
|
||||
final, initial = opts.keymap, default_opts.keymap
|
||||
final = {(k,): v for k, v in final.items()}
|
||||
initial = {(k,): v for k, v in initial.items()}
|
||||
final_s, initial_s = map(flatten_sequence_map, (opts.sequence_map, default_opts.sequence_map))
|
||||
final.update(final_s)
|
||||
initial.update(initial_s)
|
||||
compare_keymaps(final, initial)
|
||||
|
||||
|
||||
def create_opts(args, debug_config=False):
|
||||
config = tuple(resolve_config(args.config))
|
||||
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
|
||||
if debug_config:
|
||||
print(version(add_rev=True))
|
||||
print(' '.join(os.uname()))
|
||||
if is_macos:
|
||||
import subprocess
|
||||
print(' '.join(subprocess.check_output(['sw_vers']).decode('utf-8').splitlines()).strip())
|
||||
else:
|
||||
print('Running under:', green('Wayland' if is_wayland else 'X11'))
|
||||
|
||||
@@ -106,6 +106,14 @@ def screen_backspace():
|
||||
write('\x08')
|
||||
|
||||
|
||||
def screen_set_cursor(mode, secondary):
|
||||
write(CSI + '%d q' % secondary)
|
||||
|
||||
|
||||
def screen_insert_lines(num):
|
||||
write(CSI + '%dL' % num)
|
||||
|
||||
|
||||
def draw(*a):
|
||||
write(' '.join(a))
|
||||
|
||||
@@ -130,7 +138,7 @@ set_dynamic_color = set_color_table_color = write_osc
|
||||
|
||||
def replay(raw):
|
||||
for line in raw.splitlines():
|
||||
if line.strip():
|
||||
if line.strip() and not line.startswith('#'):
|
||||
cmd, rest = line.partition(' ')[::2]
|
||||
if cmd in {'draw', 'set_title', 'set_icon', 'set_dynamic_color', 'set_color_table_color'}:
|
||||
globals()[cmd](rest)
|
||||
|
||||
@@ -21,7 +21,7 @@ class MatchError(ValueError):
|
||||
ValueError.__init__(self, 'No matching {} for expression: {}'.format(target, expression))
|
||||
|
||||
|
||||
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...'):
|
||||
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...', string_return_is_error=False):
|
||||
|
||||
def w(func):
|
||||
func.short_desc = short_desc
|
||||
@@ -32,6 +32,7 @@ def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='..
|
||||
func.is_cmd = True
|
||||
func.impl = lambda: globals()[func.__name__[4:]]
|
||||
func.no_response = no_response
|
||||
func.string_return_is_error = string_return_is_error
|
||||
return func
|
||||
return w
|
||||
|
||||
@@ -39,10 +40,13 @@ def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='..
|
||||
MATCH_WINDOW_OPTION = '''\
|
||||
--match -m
|
||||
The window to match. Match specifications are of the form:
|
||||
|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline.
|
||||
|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline, num.
|
||||
You can use the |_ ls| command to get a list of windows. Note that for
|
||||
numeric fields such as id and pid the expression is interpreted as a number,
|
||||
not a regular expression.
|
||||
numeric fields such as id, pid and num the expression is interpreted as a number,
|
||||
not a regular expression. The field num refers to the window position in the current tab,
|
||||
starting from zero and counting clockwise (this is the same as the order in which the
|
||||
windows are reported by the |_ ls| command). The window id of the current window
|
||||
is available as the KITTY_WINDOW_ID environment variable.
|
||||
'''
|
||||
MATCH_TAB_OPTION = '''\
|
||||
--match -m
|
||||
@@ -272,6 +276,55 @@ def close_window(boss, window, payload):
|
||||
# }}}
|
||||
|
||||
|
||||
# resize_window {{{
|
||||
@cmd(
|
||||
'Resize the specified window',
|
||||
'Resize the specified window in the current layout. Note that not all layouts can resize all windows in all directions.',
|
||||
options_spec=MATCH_WINDOW_OPTION + '''\n
|
||||
--increment -i
|
||||
type=int
|
||||
default=2
|
||||
The number of cells to change the size by, can be negative to decrease the size.
|
||||
|
||||
|
||||
--axis -a
|
||||
type=choices
|
||||
choices=horizontal,vertical,reset
|
||||
default=horizontal
|
||||
The axis along which to resize. If |_ horizontal|, it will make the window wider or narrower by the specified increment.
|
||||
If |_ vertical|, it will make the window taller or shorter by the specified increment. The special value |_ reset| will
|
||||
reset the layout to its default configuration.
|
||||
|
||||
|
||||
--self
|
||||
type=bool-set
|
||||
If specified close the window this command is run in, rather than the active window.
|
||||
''',
|
||||
argspec='',
|
||||
string_return_is_error=True
|
||||
)
|
||||
def cmd_resize_window(global_opts, opts, args):
|
||||
return {'match': opts.match, 'increment': opts.increment, 'axis': opts.axis, 'self': opts.self}
|
||||
|
||||
|
||||
def resize_window(boss, window, payload):
|
||||
match = payload['match']
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and payload['self'] else boss.active_window]
|
||||
resized = False
|
||||
if windows and windows[0]:
|
||||
resized = boss.resize_layout_window(
|
||||
windows[0], increment=payload['increment'], is_horizontal=payload['axis'] == 'horizontal',
|
||||
reset=payload['axis'] == 'reset'
|
||||
)
|
||||
return resized
|
||||
# }}}
|
||||
|
||||
|
||||
# close_tab {{{
|
||||
@cmd(
|
||||
'Close the specified tab(s)',
|
||||
@@ -372,6 +425,7 @@ def new_window(boss, window, payload):
|
||||
# focus_window {{{
|
||||
@cmd(
|
||||
'Focus the specified window',
|
||||
'Focus the specified window, if no window is specified, focus the window this command is run inside.',
|
||||
options_spec=MATCH_WINDOW_OPTION,
|
||||
argspec='',
|
||||
)
|
||||
|
||||
@@ -23,8 +23,6 @@ typedef void* rusage_info_t; // needed for libproc.h
|
||||
#endif
|
||||
|
||||
static NSMenuItem* title_menu = NULL;
|
||||
static bool change_titlebar_color = false;
|
||||
static color_type titlebar_color = 0;
|
||||
|
||||
|
||||
static NSString*
|
||||
@@ -207,32 +205,14 @@ cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
|
||||
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
|
||||
}
|
||||
|
||||
static inline color_type
|
||||
color_as_int(PyObject *color) {
|
||||
#define I(n, s) ((PyLong_AsUnsignedLong(PyTuple_GET_ITEM(color, n)) & 0xff) << s)
|
||||
return (I(0, 16) | I(1, 8) | I(2, 0)) & 0xffffff;
|
||||
#undef I
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
macos_change_titlebar_color(PyObject *self UNUSED, PyObject *val) {
|
||||
if (val == Py_None) change_titlebar_color = false;
|
||||
else if (val == Py_True) {
|
||||
change_titlebar_color = true;
|
||||
titlebar_color = OPT(background);
|
||||
} else {
|
||||
if (!PyTuple_Check(val)) { PyErr_SetString(PyExc_TypeError, "Not a color tuple"); return NULL; }
|
||||
change_titlebar_color = true;
|
||||
titlebar_color = color_as_int(val);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
void
|
||||
cocoa_set_hide_from_tasks(void) {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_set_titlebar_color(void *w)
|
||||
cocoa_set_titlebar_color(void *w, color_type titlebar_color)
|
||||
{
|
||||
if (!change_titlebar_color) return;
|
||||
|
||||
NSWindow *window = (NSWindow *)w;
|
||||
|
||||
double red = ((titlebar_color >> 16) & 0xFF) / 255.0;
|
||||
@@ -259,7 +239,6 @@ cocoa_set_titlebar_color(void *w)
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
|
||||
{"macos_change_titlebar_color", (PyCFunction)macos_change_titlebar_color, METH_O, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
240
kitty/config.py
240
kitty/config.py
@@ -2,25 +2,23 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import ast
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import tempfile
|
||||
from collections import namedtuple
|
||||
from contextlib import contextmanager
|
||||
|
||||
from . import fast_data_types as defines
|
||||
from .config_utils import (
|
||||
init_config, parse_config_base, positive_float, positive_int, to_bool,
|
||||
to_color, unit_float
|
||||
init_config, load_config as _load_config, merge_dicts, parse_config_base,
|
||||
positive_float, positive_int, python_string, to_bool, to_cmdline, to_color,
|
||||
unit_float
|
||||
)
|
||||
from .constants import cache_dir, defconf
|
||||
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
|
||||
from .layout import all_layouts
|
||||
from .rgb import color_from_int
|
||||
from .rgb import color_as_int, color_from_int
|
||||
from .utils import log_error
|
||||
|
||||
MINIMUM_FONT_SIZE = 4
|
||||
@@ -48,18 +46,20 @@ def to_cursor_shape(x):
|
||||
)
|
||||
|
||||
|
||||
def parse_mods(parts):
|
||||
mod_map = {'CTRL': 'CONTROL', 'CMD': 'SUPER', '⌘': 'SUPER', '⌥': 'ALT', 'OPTION': 'ALT', 'KITTY_MOD': 'KITTY'}
|
||||
|
||||
|
||||
def parse_mods(parts, sc):
|
||||
|
||||
def map_mod(m):
|
||||
return {'CTRL': 'CONTROL', 'CMD': 'SUPER', '⌘': 'SUPER', '⌥': 'ALT', 'OPTION': 'ALT'}.get(m, m)
|
||||
return mod_map.get(m, m)
|
||||
|
||||
mods = 0
|
||||
for m in parts:
|
||||
try:
|
||||
mods |= getattr(defines, 'GLFW_MOD_' + map_mod(m.upper()))
|
||||
except AttributeError:
|
||||
log_error('Shortcut: {} has unknown modifier, ignoring'.format(
|
||||
parts.join('+')))
|
||||
log_error('Shortcut: {} has unknown modifier, ignoring'.format(sc))
|
||||
return
|
||||
|
||||
return mods
|
||||
@@ -81,12 +81,14 @@ named_keys = {
|
||||
|
||||
def parse_shortcut(sc):
|
||||
parts = sc.split('+')
|
||||
mods = parse_mods(parts[:-1])
|
||||
mods = parse_mods(parts[:-1], sc)
|
||||
if mods is None:
|
||||
return None, None
|
||||
key = parts[-1].upper()
|
||||
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
||||
if key is not None:
|
||||
return mods, key
|
||||
return None, None
|
||||
return mods, None
|
||||
|
||||
|
||||
KeyAction = namedtuple('KeyAction', 'func args')
|
||||
@@ -108,10 +110,18 @@ def parse_key_action(action):
|
||||
args = tuple(map(parse_key_action, filter(None, parts)))
|
||||
elif func == 'send_text':
|
||||
args = rest.split(' ', 1)
|
||||
elif func in ('run_kitten', 'run_simple_kitten'):
|
||||
if func == 'run_simple_kitten':
|
||||
func = 'run_kitten'
|
||||
args = rest.split(' ', 2)
|
||||
if len(args) > 0:
|
||||
try:
|
||||
args[1] = parse_send_text_bytes(args[1])
|
||||
except Exception:
|
||||
log_error('Ignoring invalid send_text string: ' + args[1])
|
||||
args[1] = ''
|
||||
elif func in ('run_kitten', 'run_simple_kitten', 'kitten'):
|
||||
if func == 'kitten':
|
||||
args = rest.split(' ', 1)
|
||||
else:
|
||||
args = rest.split(' ', 2)[1:]
|
||||
func = 'kitten'
|
||||
elif func == 'goto_tab':
|
||||
args = (max(0, int(rest)), )
|
||||
elif func == 'goto_layout' or func == 'kitty_shell':
|
||||
@@ -119,7 +129,7 @@ def parse_key_action(action):
|
||||
elif func == 'set_font_size':
|
||||
args = (float(rest),)
|
||||
elif func in shlex_actions:
|
||||
args = shlex.split(rest)
|
||||
args = to_cmdline(rest)
|
||||
return KeyAction(func, args)
|
||||
|
||||
|
||||
@@ -127,7 +137,20 @@ all_key_actions = set()
|
||||
sequence_sep = '>'
|
||||
|
||||
|
||||
def parse_key(val, keymap, sequence_map):
|
||||
class KeyDefinition:
|
||||
|
||||
def __init__(self, is_sequence, action, mods, key, rest=()):
|
||||
self.is_sequence = is_sequence
|
||||
self.action = action
|
||||
self.trigger = mods, key
|
||||
self.rest = rest
|
||||
|
||||
def resolve(self, kitty_mod):
|
||||
self.trigger = defines.resolve_key_mods(kitty_mod, self.trigger[0]), self.trigger[1]
|
||||
self.rest = tuple((defines.resolve_key_mods(kitty_mod, mods), key) for mods, key in self.rest)
|
||||
|
||||
|
||||
def parse_key(val, key_definitions):
|
||||
sc, action = val.partition(' ')[::2]
|
||||
sc, action = sc.strip().strip(sequence_sep), action.strip()
|
||||
if not sc or not action:
|
||||
@@ -139,8 +162,8 @@ def parse_key(val, keymap, sequence_map):
|
||||
for part in sc.split(sequence_sep):
|
||||
mods, key = parse_shortcut(part)
|
||||
if key is None:
|
||||
log_error('Shortcut: {} has unknown key, ignoring'.format(
|
||||
sc))
|
||||
if mods is not None:
|
||||
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
|
||||
return
|
||||
if trigger is None:
|
||||
trigger = mods, key
|
||||
@@ -150,8 +173,8 @@ def parse_key(val, keymap, sequence_map):
|
||||
else:
|
||||
mods, key = parse_shortcut(sc)
|
||||
if key is None:
|
||||
log_error('Shortcut: {} has unknown key, ignoring'.format(
|
||||
sc))
|
||||
if mods is not None:
|
||||
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
|
||||
return
|
||||
try:
|
||||
paction = parse_key_action(action)
|
||||
@@ -162,10 +185,9 @@ def parse_key(val, keymap, sequence_map):
|
||||
if paction is not None:
|
||||
all_key_actions.add(paction.func)
|
||||
if is_sequence:
|
||||
s = sequence_map.setdefault(trigger, {})
|
||||
s[rest] = paction
|
||||
key_definitions.append(KeyDefinition(True, paction, trigger[0], trigger[1], rest))
|
||||
else:
|
||||
keymap[(mods, key)] = paction
|
||||
key_definitions.append(KeyDefinition(False, paction, mods, key))
|
||||
|
||||
|
||||
def parse_symbol_map(val):
|
||||
@@ -201,11 +223,10 @@ def parse_symbol_map(val):
|
||||
|
||||
|
||||
def parse_send_text_bytes(text):
|
||||
return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''"
|
||||
).encode('utf-8')
|
||||
return python_string(text).encode('utf-8')
|
||||
|
||||
|
||||
def parse_send_text(val, keymap, sequence_map):
|
||||
def parse_send_text(val, key_definitions):
|
||||
parts = val.split(' ')
|
||||
|
||||
def abort(msg):
|
||||
@@ -218,21 +239,31 @@ def parse_send_text(val, keymap, sequence_map):
|
||||
mode, sc = parts[:2]
|
||||
text = ' '.join(parts[2:])
|
||||
key_str = '{} send_text {} {}'.format(sc, mode, text)
|
||||
return parse_key(key_str, keymap, sequence_map)
|
||||
return parse_key(key_str, key_definitions)
|
||||
|
||||
|
||||
def to_modifiers(val):
|
||||
return parse_mods(val.split('+'))
|
||||
return parse_mods(val.split('+'), val) or 0
|
||||
|
||||
|
||||
def uniq(vals, result_type=list):
|
||||
seen = set()
|
||||
seen_add = seen.add
|
||||
return result_type(x for x in vals if x not in seen and not seen_add(x))
|
||||
|
||||
|
||||
def to_layout_names(raw):
|
||||
parts = [x.strip().lower() for x in raw.split(',')]
|
||||
if '*' in parts:
|
||||
return sorted(all_layouts)
|
||||
ans = []
|
||||
for p in parts:
|
||||
if p not in all_layouts:
|
||||
if p == '*':
|
||||
ans.extend(sorted(all_layouts))
|
||||
continue
|
||||
name = p.partition(':')[0]
|
||||
if name not in all_layouts:
|
||||
raise ValueError('The window layout {} is unknown'.format(p))
|
||||
return parts
|
||||
ans.append(p)
|
||||
return uniq(ans)
|
||||
|
||||
|
||||
def adjust_line_height(x):
|
||||
@@ -244,10 +275,10 @@ def adjust_line_height(x):
|
||||
def macos_titlebar_color(x):
|
||||
x = x.strip('"')
|
||||
if x == 'system':
|
||||
return
|
||||
return 0
|
||||
if x == 'background':
|
||||
return True
|
||||
return to_color(x)
|
||||
return 1
|
||||
return (color_as_int(to_color(x)) << 8) | 2
|
||||
|
||||
|
||||
def box_drawing_scale(x):
|
||||
@@ -292,7 +323,8 @@ type_map = {
|
||||
'adjust_line_height': adjust_line_height,
|
||||
'adjust_column_width': adjust_line_height,
|
||||
'scrollback_lines': positive_int,
|
||||
'scrollback_pager': shlex.split,
|
||||
'scrollback_pager': to_cmdline,
|
||||
'open_url_with': to_cmdline,
|
||||
'font_size': to_font_size,
|
||||
'font_size_delta': positive_float,
|
||||
'focus_follows_mouse': to_bool,
|
||||
@@ -318,22 +350,31 @@ type_map = {
|
||||
'initial_window_width': positive_int,
|
||||
'initial_window_height': positive_int,
|
||||
'macos_hide_titlebar': to_bool,
|
||||
'macos_hide_from_tasks': to_bool,
|
||||
'macos_option_as_alt': to_bool,
|
||||
'macos_titlebar_color': macos_titlebar_color,
|
||||
'box_drawing_scale': box_drawing_scale,
|
||||
'background_opacity': unit_float,
|
||||
'dim_opacity': unit_float,
|
||||
'tab_separator': tab_separator,
|
||||
'active_tab_font_style': tab_font_style,
|
||||
'inactive_tab_font_style': tab_font_style,
|
||||
'inactive_text_alpha': unit_float,
|
||||
'url_style': url_style,
|
||||
'copy_on_select': to_bool,
|
||||
'window_alert_on_bell': to_bool,
|
||||
'tab_bar_edge': tab_bar_edge,
|
||||
'bell_on_tab': to_bool,
|
||||
'kitty_mod': to_modifiers,
|
||||
'clear_all_shortcuts': to_bool,
|
||||
'clipboard_control': lambda x: frozenset(x.lower().split()),
|
||||
'window_resize_step_cells': int,
|
||||
'window_resize_step_lines': int,
|
||||
}
|
||||
|
||||
for name in (
|
||||
'foreground background cursor active_border_color inactive_border_color'
|
||||
' selection_foreground selection_background url_color'
|
||||
' selection_foreground selection_background url_color bell_border_color'
|
||||
).split():
|
||||
type_map[name] = to_color
|
||||
for i in range(256):
|
||||
@@ -345,15 +386,19 @@ for a in ('active', 'inactive'):
|
||||
|
||||
def special_handling(key, val, ans):
|
||||
if key == 'map':
|
||||
parse_key(val, ans['keymap'], ans['sequence_map'])
|
||||
parse_key(val, ans['key_definitions'])
|
||||
return True
|
||||
if key == 'symbol_map':
|
||||
ans['symbol_map'].update(parse_symbol_map(val))
|
||||
return True
|
||||
if key == 'send_text':
|
||||
# For legacy compatibility
|
||||
parse_send_text(val, ans['keymap'], ans['sequence_map'])
|
||||
parse_send_text(val, ans['key_definitions'])
|
||||
return True
|
||||
if key == 'clear_all_shortcuts':
|
||||
if to_bool(val):
|
||||
ans['key_definitions'] = [None]
|
||||
return
|
||||
|
||||
|
||||
defaults = None
|
||||
@@ -363,11 +408,7 @@ default_config_path = os.path.join(
|
||||
|
||||
|
||||
def parse_config(lines, check_keys=True):
|
||||
ans = {
|
||||
'keymap': {},
|
||||
'sequence_map': {},
|
||||
'symbol_map': {},
|
||||
}
|
||||
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': []}
|
||||
parse_config_base(
|
||||
lines,
|
||||
defaults,
|
||||
@@ -397,74 +438,20 @@ actions = frozenset(all_key_actions) | frozenset(
|
||||
no_op_actions = frozenset({'noop', 'no-op', 'no_op'})
|
||||
|
||||
|
||||
def merge_keys(ans, defaults, newvals):
|
||||
ans['keymap'] = defaults['keymap'].copy()
|
||||
ans['sequence_map'] = {t: r.copy() for t, r in defaults['sequence_map'].items()}
|
||||
# Merge the keymap
|
||||
for k, v in newvals['keymap'].items():
|
||||
ans['sequence_map'].pop(k, None)
|
||||
f = v.func
|
||||
if f in no_op_actions:
|
||||
ans['keymap'].pop(k, None)
|
||||
elif f in actions:
|
||||
ans['keymap'][k] = v
|
||||
# Merge the sequence map
|
||||
for trigger, rest_map in newvals['sequence_map'].items():
|
||||
ans['keymap'].pop(trigger, None)
|
||||
if trigger in newvals['keymap']:
|
||||
log_error('The shortcut for {} has conflicting definitions'.format(newvals['keymap'][trigger].func))
|
||||
s = ans['sequence_map'].setdefault(trigger, {})
|
||||
for k, v in rest_map.items():
|
||||
f = v.func
|
||||
if f in no_op_actions:
|
||||
s.pop(k, None)
|
||||
elif f in actions:
|
||||
s[k] = v
|
||||
ans['sequence_map'] = {k: v for k, v in ans['sequence_map'].items() if v}
|
||||
|
||||
|
||||
def merge_dicts(defaults, newvals):
|
||||
ans = defaults.copy()
|
||||
ans.update(newvals)
|
||||
return ans
|
||||
|
||||
|
||||
def merge_configs(defaults, vals):
|
||||
ans = {}
|
||||
for k, v in defaults.items():
|
||||
if isinstance(v, dict):
|
||||
if k not in ('keymap', 'sequence_map'):
|
||||
newvals = vals.get(k, {})
|
||||
ans[k] = merge_dicts(v, newvals)
|
||||
newvals = vals.get(k, {})
|
||||
ans[k] = merge_dicts(v, newvals)
|
||||
elif k == 'key_definitions':
|
||||
ans['key_definitions'] = v + vals.get('key_definitions', [])
|
||||
else:
|
||||
ans[k] = vals.get(k, v)
|
||||
merge_keys(
|
||||
ans,
|
||||
{'keymap': defaults.get('keymap', {}), 'sequence_map': defaults.get('sequence_map', {})},
|
||||
{'keymap': vals.get('keymap', {}), 'sequence_map': vals.get('sequence_map', {})}
|
||||
)
|
||||
return ans
|
||||
|
||||
|
||||
def load_config(*paths, overrides=None) -> Options:
|
||||
ans = defaults._asdict()
|
||||
for path in paths:
|
||||
if not path:
|
||||
continue
|
||||
try:
|
||||
f = open(path, encoding='utf-8', errors='replace')
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
with f:
|
||||
vals = parse_config(f)
|
||||
ans = merge_configs(ans, vals)
|
||||
if overrides is not None:
|
||||
vals = parse_config(overrides)
|
||||
ans = merge_configs(ans, vals)
|
||||
return Options(ans)
|
||||
|
||||
|
||||
def build_ansi_color_table(opts: Options = defaults):
|
||||
def build_ansi_color_table(opts=defaults):
|
||||
|
||||
def as_int(x):
|
||||
return (x[0] << 16) | (x[1] << 8) | x[2]
|
||||
@@ -476,6 +463,7 @@ def build_ansi_color_table(opts: Options = defaults):
|
||||
|
||||
|
||||
def atomic_save(data, path):
|
||||
import tempfile
|
||||
fd, p = tempfile.mkstemp(dir=os.path.dirname(path), suffix='.tmp')
|
||||
try:
|
||||
with os.fdopen(fd, 'wb') as f:
|
||||
@@ -547,3 +535,45 @@ def prepare_config_file_for_editing():
|
||||
with open(defconf, 'w') as f:
|
||||
f.write(commented_out_default_config())
|
||||
return defconf
|
||||
|
||||
|
||||
def finalize_keys(opts):
|
||||
defns = []
|
||||
for d in opts.key_definitions:
|
||||
if d is None: # clear_all_shortcuts
|
||||
defns = []
|
||||
else:
|
||||
defns.append(d)
|
||||
for d in defns:
|
||||
d.resolve(opts.kitty_mod)
|
||||
keymap = {}
|
||||
sequence_map = {}
|
||||
|
||||
for defn in defns:
|
||||
is_no_op = defn.action.func in no_op_actions
|
||||
if defn.is_sequence:
|
||||
keymap.pop(defn.trigger, None)
|
||||
s = sequence_map.setdefault(defn.trigger, {})
|
||||
if is_no_op:
|
||||
s.pop(defn.rest, None)
|
||||
if not s:
|
||||
del sequence_map[defn.trigger]
|
||||
else:
|
||||
s[defn.rest] = defn.action
|
||||
else:
|
||||
sequence_map.pop(defn.trigger, None)
|
||||
if is_no_op:
|
||||
keymap.pop(defn.trigger, None)
|
||||
else:
|
||||
keymap[defn.trigger] = defn.action
|
||||
opts.keymap = keymap
|
||||
opts.sequence_map = sequence_map
|
||||
|
||||
|
||||
def load_config(*paths, overrides=None):
|
||||
opts = _load_config(Options, defaults, parse_config, merge_configs, *paths, overrides=overrides)
|
||||
finalize_keys(opts)
|
||||
if opts.background_opacity < 1.0 and opts.macos_titlebar_color:
|
||||
log_error('Cannot use both macos_titlebar_color and background_opacity')
|
||||
opts.macos_titlebar_color = 0
|
||||
return opts
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
|
||||
from .rgb import to_color as as_color
|
||||
from .utils import log_error
|
||||
@@ -31,6 +33,16 @@ def to_bool(x):
|
||||
return x.lower() in 'y yes true'.split()
|
||||
|
||||
|
||||
def to_cmdline(x):
|
||||
ans = shlex.split(x)
|
||||
ans[0] = os.path.expandvars(os.path.expanduser(ans[0]))
|
||||
return ans
|
||||
|
||||
|
||||
def python_string(text):
|
||||
return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''")
|
||||
|
||||
|
||||
def parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_includes):
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
@@ -41,7 +53,7 @@ def parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_in
|
||||
if special_handling(key, val, ans):
|
||||
return
|
||||
if key == 'include':
|
||||
val = val.strip()
|
||||
val = os.path.expandvars(os.path.expanduser(val.strip()))
|
||||
if not os.path.isabs(val):
|
||||
val = os.path.join(base_path_for_includes, val)
|
||||
try:
|
||||
@@ -117,6 +129,41 @@ def create_options_class(keys):
|
||||
return ans
|
||||
|
||||
|
||||
def merge_dicts(defaults, newvals):
|
||||
ans = defaults.copy()
|
||||
ans.update(newvals)
|
||||
return ans
|
||||
|
||||
|
||||
def resolve_config(SYSTEM_CONF, defconf, config_files_on_cmd_line):
|
||||
if config_files_on_cmd_line:
|
||||
if 'NONE' not in config_files_on_cmd_line:
|
||||
yield SYSTEM_CONF
|
||||
for cf in config_files_on_cmd_line:
|
||||
yield cf
|
||||
else:
|
||||
yield SYSTEM_CONF
|
||||
yield defconf
|
||||
|
||||
|
||||
def load_config(Options, defaults, parse_config, merge_configs, *paths, overrides=None):
|
||||
ans = defaults._asdict()
|
||||
for path in paths:
|
||||
if not path:
|
||||
continue
|
||||
try:
|
||||
f = open(path, encoding='utf-8', errors='replace')
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
with f:
|
||||
vals = parse_config(f)
|
||||
ans = merge_configs(ans, vals)
|
||||
if overrides is not None:
|
||||
vals = parse_config(overrides)
|
||||
ans = merge_configs(ans, vals)
|
||||
return Options(ans)
|
||||
|
||||
|
||||
def init_config(defaults_path, parse_config):
|
||||
with open(defaults_path, encoding='utf-8', errors='replace') as f:
|
||||
defaults = parse_config(f, check_keys=False)
|
||||
|
||||
@@ -11,7 +11,7 @@ from collections import namedtuple
|
||||
from .fast_data_types import set_boss as set_c_boss
|
||||
|
||||
appname = 'kitty'
|
||||
version = (0, 9, 0)
|
||||
version = (0, 10, 0)
|
||||
str_version = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos = 'darwin' in _plat
|
||||
@@ -105,3 +105,6 @@ def glfw_path(module):
|
||||
is_wayland = False
|
||||
if os.environ.get('WAYLAND_DISPLAY') and 'KITTY_ENABLE_WAYLAND' in os.environ and os.path.exists(glfw_path('wayland')):
|
||||
is_wayland = True
|
||||
|
||||
|
||||
supports_primary_selection = not is_macos and not is_wayland
|
||||
|
||||
@@ -24,7 +24,7 @@ dealloc(Cursor* self) {
|
||||
|
||||
#define EQ(x) (a->x == b->x)
|
||||
static int __eq__(Cursor *a, Cursor *b) {
|
||||
return EQ(bold) && EQ(italic) && EQ(strikethrough) && EQ(reverse) && EQ(decoration) && EQ(fg) && EQ(bg) && EQ(decoration_fg) && EQ(x) && EQ(y) && EQ(shape) && EQ(blink);
|
||||
return EQ(bold) && EQ(italic) && EQ(strikethrough) && EQ(dim) && EQ(reverse) && EQ(decoration) && EQ(fg) && EQ(bg) && EQ(decoration_fg) && EQ(x) && EQ(y) && EQ(shape) && EQ(blink);
|
||||
}
|
||||
|
||||
static const char* cursor_names[NUM_OF_CURSOR_SHAPES] = { "NO_SHAPE", "BLOCK", "BEAM", "UNDERLINE" };
|
||||
@@ -33,16 +33,16 @@ static const char* cursor_names[NUM_OF_CURSOR_SHAPES] = { "NO_SHAPE", "BLOCK", "
|
||||
static PyObject *
|
||||
repr(Cursor *self) {
|
||||
return PyUnicode_FromFormat(
|
||||
"Cursor(x=%u, y=%u, shape=%s, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, decoration=%d, decoration_fg=#%08x)",
|
||||
"Cursor(x=%u, y=%u, shape=%s, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, dim=%R, decoration=%d, decoration_fg=#%08x)",
|
||||
self->x, self->y, (self->shape < NUM_OF_CURSOR_SHAPES ? cursor_names[self->shape] : "INVALID"),
|
||||
BOOL(self->blink), self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), self->decoration, self->decoration_fg
|
||||
BOOL(self->blink), self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), BOOL(self->dim), self->decoration, self->decoration_fg
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
cursor_reset_display_attrs(Cursor *self) {
|
||||
self->bg = 0; self->fg = 0; self->decoration_fg = 0;
|
||||
self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false;
|
||||
self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false; self->dim = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,8 @@ START_ALLOW_CASE_RANGE
|
||||
cursor_reset_display_attrs(self); break;
|
||||
case 1:
|
||||
self->bold = true; break;
|
||||
case 2:
|
||||
self->dim = true; break;
|
||||
case 3:
|
||||
self->italic = true; break;
|
||||
case 4:
|
||||
@@ -98,7 +100,7 @@ START_ALLOW_CASE_RANGE
|
||||
case 21:
|
||||
self->decoration = 2; break;
|
||||
case 22:
|
||||
self->bold = false; break;
|
||||
self->bold = false; self->dim = false; break;
|
||||
case 23:
|
||||
self->italic = false; break;
|
||||
case 24:
|
||||
@@ -138,6 +140,7 @@ apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *para
|
||||
#define RANGE for(unsigned c = 0; c < cell_count; c++, cell++)
|
||||
#define SET(shift) RANGE { cell->attrs |= (1 << shift); } break;
|
||||
#define RESET(shift) RANGE { cell->attrs &= ~(1 << shift); } break;
|
||||
#define RESET2(shift1, shift2) RANGE { cell->attrs &= ~((1 << shift1) | (1 << shift2)); } break;
|
||||
#define SETM(val, mask, shift) { RANGE { cell->attrs &= ~(mask << shift); cell->attrs |= ((val) << shift); } break; }
|
||||
#define SET_COLOR(which) { color_type color = 0; parse_color(params, &i, count, &color); if (color) { RANGE { cell->which = color; }} } break;
|
||||
#define SIMPLE(which, val) RANGE { cell->which = (val); } break;
|
||||
@@ -153,6 +156,8 @@ apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *para
|
||||
break;
|
||||
case 1:
|
||||
SET(BOLD_SHIFT);
|
||||
case 2:
|
||||
SET(DIM_SHIFT);
|
||||
case 3:
|
||||
SET(ITALIC_SHIFT);
|
||||
case 4:
|
||||
@@ -165,7 +170,7 @@ apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *para
|
||||
case 21:
|
||||
SETM(2, DECORATION_MASK, DECORATION_SHIFT);
|
||||
case 22:
|
||||
RESET(BOLD_SHIFT);
|
||||
RESET2(DIM_SHIFT, BOLD_SHIFT);
|
||||
case 23:
|
||||
RESET(ITALIC_SHIFT);
|
||||
case 24:
|
||||
@@ -199,6 +204,7 @@ END_ALLOW_CASE_RANGE
|
||||
}
|
||||
}
|
||||
#undef RESET
|
||||
#undef RESET2
|
||||
#undef SET_COLOR
|
||||
#undef SET
|
||||
#undef SETM
|
||||
@@ -237,7 +243,11 @@ cursor_as_sgr(Cursor *self, Cursor *prev) {
|
||||
#define SZ sizeof(buf) - (p - buf) - 2
|
||||
#define P(fmt, ...) { p += snprintf(p, SZ, fmt ";", __VA_ARGS__); }
|
||||
char *p = buf;
|
||||
if (self->bold != prev->bold) P("%d", self->bold ? 1 : 22);
|
||||
bool intensity_differs = self->bold != prev->bold || self->dim != prev->dim;
|
||||
if (intensity_differs) {
|
||||
if (!self->bold || !self->dim) { P("%d", 22); }
|
||||
else P("%d;%d", 1, 2);
|
||||
}
|
||||
if (self->italic != prev->italic) P("%d", self->italic ? 3 : 23);
|
||||
if (self->reverse != prev->reverse) P("%d", self->reverse ? 7 : 27);
|
||||
if (self->strikethrough != prev->strikethrough) P("%d", self->strikethrough ? 9 : 29);
|
||||
@@ -268,7 +278,7 @@ void cursor_reset(Cursor *self) {
|
||||
void cursor_copy_to(Cursor *src, Cursor *dest) {
|
||||
#define CCY(x) dest->x = src->x;
|
||||
CCY(x); CCY(y); CCY(shape); CCY(blink);
|
||||
CCY(bold); CCY(italic); CCY(strikethrough); CCY(reverse); CCY(decoration); CCY(fg); CCY(bg); CCY(decoration_fg);
|
||||
CCY(bold); CCY(italic); CCY(strikethrough); CCY(dim); CCY(reverse); CCY(decoration); CCY(fg); CCY(bg); CCY(decoration_fg);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
@@ -281,6 +291,7 @@ BOOL_GETSET(Cursor, bold)
|
||||
BOOL_GETSET(Cursor, italic)
|
||||
BOOL_GETSET(Cursor, reverse)
|
||||
BOOL_GETSET(Cursor, strikethrough)
|
||||
BOOL_GETSET(Cursor, dim)
|
||||
BOOL_GETSET(Cursor, blink)
|
||||
|
||||
static PyMemberDef members[] = {
|
||||
@@ -299,6 +310,7 @@ static PyGetSetDef getseters[] = {
|
||||
GETSET(italic)
|
||||
GETSET(reverse)
|
||||
GETSET(strikethrough)
|
||||
GETSET(dim)
|
||||
GETSET(blink)
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
@@ -193,14 +193,11 @@ PyInit_fast_data_types(void) {
|
||||
#endif
|
||||
if (!init_fonts(m)) return NULL;
|
||||
|
||||
#define OOF(n) #n, offsetof(Cell, n)
|
||||
if (PyModule_AddObject(m, "CELL", Py_BuildValue("{sI sI sI sI sI sI sI sI sI}",
|
||||
OOF(ch), OOF(fg), OOF(bg), OOF(decoration_fg), OOF(cc_idx), OOF(sprite_x), OOF(sprite_y), OOF(sprite_z), "size", sizeof(Cell))) != 0) return NULL;
|
||||
#undef OOF
|
||||
PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
|
||||
PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);
|
||||
PyModule_AddIntConstant(m, "REVERSE", REVERSE_SHIFT);
|
||||
PyModule_AddIntConstant(m, "STRIKETHROUGH", STRIKE_SHIFT);
|
||||
PyModule_AddIntConstant(m, "DIM", DIM_SHIFT);
|
||||
PyModule_AddIntConstant(m, "DECORATION", DECORATION_SHIFT);
|
||||
PyModule_AddStringMacro(m, ERROR_PREFIX);
|
||||
#ifdef KITTY_VCS_REV
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// Required minimum OpenGL version
|
||||
#define OPENGL_REQUIRED_VERSION_MAJOR 3
|
||||
#define OPENGL_REQUIRED_VERSION_MINOR 3
|
||||
#define GLFW_MOD_KITTY 1024
|
||||
#define UNUSED __attribute__ ((unused))
|
||||
#define PYNOARG PyObject *__a1 UNUSED, PyObject *__a2 UNUSED
|
||||
#define EXPORTED __attribute__ ((visibility ("default")))
|
||||
@@ -57,6 +58,7 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
|
||||
#define BI_VAL(attrs) ((attrs >> 4) & 3)
|
||||
#define REVERSE_SHIFT 6
|
||||
#define STRIKE_SHIFT 7
|
||||
#define DIM_SHIFT 8
|
||||
#define COL_MASK 0xFFFFFFFF
|
||||
#define UTF8_ACCEPT 0
|
||||
#define UTF8_REJECT 1
|
||||
@@ -70,11 +72,12 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
|
||||
|
||||
#define CURSOR_TO_ATTRS(c, w) \
|
||||
((w) | (((c->decoration & 3) << DECORATION_SHIFT) | ((c->bold & 1) << BOLD_SHIFT) | \
|
||||
((c->italic & 1) << ITALIC_SHIFT) | ((c->reverse & 1) << REVERSE_SHIFT) | ((c->strikethrough & 1) << STRIKE_SHIFT)))
|
||||
((c->italic & 1) << ITALIC_SHIFT) | ((c->reverse & 1) << REVERSE_SHIFT) | \
|
||||
((c->strikethrough & 1) << STRIKE_SHIFT) | ((c->dim & 1) << DIM_SHIFT)))
|
||||
|
||||
#define ATTRS_TO_CURSOR(a, c) \
|
||||
(c)->decoration = (a >> DECORATION_SHIFT) & 3; (c)->bold = (a >> BOLD_SHIFT) & 1; (c)->italic = (a >> ITALIC_SHIFT) & 1; \
|
||||
(c)->reverse = (a >> REVERSE_SHIFT) & 1; (c)->strikethrough = (a >> STRIKE_SHIFT) & 1;
|
||||
(c)->reverse = (a >> REVERSE_SHIFT) & 1; (c)->strikethrough = (a >> STRIKE_SHIFT) & 1; (c)->dim = (a >> DIM_SHIFT) & 1;
|
||||
|
||||
#define COPY_CELL(src, s, dest, d) \
|
||||
(dest)->cells[d] = (src)->cells[s];
|
||||
@@ -160,21 +163,24 @@ typedef struct {
|
||||
Line *line;
|
||||
} LineBuf;
|
||||
|
||||
typedef struct {
|
||||
Cell *cells;
|
||||
line_attrs_type *line_attrs;
|
||||
} HistoryBufSegment;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
Cell *buf;
|
||||
index_type xnum, ynum;
|
||||
index_type xnum, ynum, num_segments;
|
||||
HistoryBufSegment* segments;
|
||||
Line *line;
|
||||
index_type start_of_data, count;
|
||||
line_attrs_type *line_attrs;
|
||||
} HistoryBuf;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
bool bold, italic, reverse, strikethrough, blink;
|
||||
bool bold, italic, reverse, strikethrough, blink, dim;
|
||||
unsigned int x, y;
|
||||
uint8_t decoration;
|
||||
CursorShape shape;
|
||||
|
||||
2
kitty/emoji.h
generated
2
kitty/emoji.h
generated
@@ -1,4 +1,4 @@
|
||||
// unicode data, built from the unicode standard on: 2018-02-09
|
||||
// unicode data, built from the unicode standard on: 2018-05-01
|
||||
// see gen-wcwidth.py
|
||||
#pragma once
|
||||
#include "data-types.h"
|
||||
|
||||
@@ -424,6 +424,8 @@ START_ALLOW_CASE_RANGE
|
||||
case 0x2574 ... 0x259f:
|
||||
case 0xe0b0:
|
||||
case 0xe0b2:
|
||||
case 0xe0b4:
|
||||
case 0xe0b6:
|
||||
return BOX_FONT;
|
||||
default:
|
||||
ans = in_symbol_maps(cell->ch);
|
||||
@@ -460,6 +462,10 @@ START_ALLOW_CASE_RANGE
|
||||
return 0xfa;
|
||||
case 0xe0b2:
|
||||
return 0xfb;
|
||||
case 0xe0b4:
|
||||
return 0xfc;
|
||||
case 0xe0b6:
|
||||
return 0xfd;
|
||||
default:
|
||||
return 0xff;
|
||||
}
|
||||
@@ -720,7 +726,8 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) {
|
||||
num_codepoints_used_by_glyph = UINT32_MAX;
|
||||
} else {
|
||||
next_cluster = G(info)[G(glyph_idx) + 1].cluster;
|
||||
if (next_cluster > cluster) num_codepoints_used_by_glyph = next_cluster - cluster;
|
||||
// RTL languages like Arabic have decreasing cluster numbers
|
||||
if (next_cluster != cluster) num_codepoints_used_by_glyph = cluster > next_cluster ? cluster - next_cluster : next_cluster - cluster;
|
||||
}
|
||||
if (!current_group->num_glyphs) {
|
||||
add_to_current_group = true;
|
||||
@@ -852,7 +859,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
|
||||
|
||||
PyObject *eg = PyTuple_New(MAX_NUM_EXTRA_GLYPHS);
|
||||
for (size_t g = 0; g < MAX_NUM_EXTRA_GLYPHS; g++) PyTuple_SET_ITEM(eg, g, Py_BuildValue("H", g + 1 < group->num_glyphs ? G(info)[group->first_glyph_idx + g].codepoint : 0));
|
||||
PyList_Append(ans, Py_BuildValue("IIIN", group->num_cells, group->num_glyphs, first_glyph, eg));
|
||||
PyList_Append(ans, Py_BuildValue("IIHN", group->num_cells, group->num_glyphs, first_glyph, eg));
|
||||
idx++;
|
||||
}
|
||||
if (face) { Py_CLEAR(face); free(font); }
|
||||
@@ -897,6 +904,12 @@ render_line(Line *line) {
|
||||
ssize_t cell_font_idx = font_for_cell(cell);
|
||||
if (is_private_use(cell->ch) && i + 1 < line->xnum && (line->cells[i+1].ch == ' ' || line->cells[i+1].ch == 0) && cell_font_idx != BOX_FONT && cell_font_idx != MISSING_FONT) {
|
||||
// We have a private use char followed by a space char, render it as a two cell ligature.
|
||||
Cell *space_cell = line->cells + i+1;
|
||||
// Ensure the space cell uses the foreground colors from the PUA cell
|
||||
// This is needed because there are stupid applications like
|
||||
// powerline that use PUA+space with different foreground colors
|
||||
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
|
||||
space_cell->fg = cell->fg; space_cell->decoration_fg = cell->decoration_fg;
|
||||
RENDER;
|
||||
render_run(line->cells + i, 2, cell_font_idx, true);
|
||||
run_font_idx = NO_FONT;
|
||||
@@ -982,7 +995,7 @@ static PyObject*
|
||||
test_sprite_position_for(PyObject UNUSED *self, PyObject *args) {
|
||||
glyph_index glyph;
|
||||
ExtraGlyphs extra_glyphs = {{0}};
|
||||
if (!PyArg_ParseTuple(args, "H|I", &glyph, &extra_glyphs.data)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "H|H", &glyph, &extra_glyphs.data)) return NULL;
|
||||
int error;
|
||||
SpritePosition *pos = sprite_position_for(&fonts.fonts[fonts.medium_font_idx], glyph, &extra_glyphs, 0, &error);
|
||||
if (pos == NULL) { sprite_map_set_error(error); return NULL; }
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import math
|
||||
import os
|
||||
from functools import partial as p
|
||||
from itertools import repeat
|
||||
|
||||
@@ -138,6 +137,20 @@ def cross(*s, a=1, b=1, c=1, d=1):
|
||||
half_vline(*s, level=d, which='bottom')
|
||||
|
||||
|
||||
def fill_region(buf, width, height, xlimits):
|
||||
for y in range(height):
|
||||
offset = y * width
|
||||
for x, (upper, lower) in enumerate(xlimits):
|
||||
buf[x + offset] = 255 if upper <= y <= lower else 0
|
||||
# Anti-alias the boundary, simple y-axis anti-aliasing
|
||||
for x, limits in enumerate(xlimits):
|
||||
for y in limits:
|
||||
for ypx in range(int(math.floor(y)), int(math.ceil(y)) + 1):
|
||||
if 0 <= ypx < height:
|
||||
off = ypx * width + x
|
||||
buf[off] = min(255, buf[off] + int((1 - abs(y - ypx)) * 255))
|
||||
|
||||
|
||||
def line_equation(x1, y1, x2, y2):
|
||||
m = (y2 - y1) / (x2 - x1)
|
||||
c = y1 - m * x1
|
||||
@@ -157,17 +170,91 @@ def triangle(buf, width, height, left=True):
|
||||
uppery = line_equation(x1, ay1, x2, y2)
|
||||
lowery = line_equation(x1, by1, x2, y2)
|
||||
xlimits = [(uppery(x), lowery(x)) for x in range(width)]
|
||||
for y in range(height):
|
||||
offset = y * width
|
||||
for x, (upper, lower) in zip(range(width), xlimits):
|
||||
buf[x + offset] = 255 if upper <= y <= lower else 0
|
||||
# Anti-alias the diagonals, simple y-axis anti-aliasing
|
||||
for x in range(width):
|
||||
for y in xlimits[x]:
|
||||
for ypx in range(int(math.floor(y)), int(math.ceil(y)) + 1):
|
||||
if 0 <= ypx < height:
|
||||
off = ypx * width + x
|
||||
buf[off] = min(255, buf[off] + int((1 - abs(y - ypx)) * 255))
|
||||
fill_region(buf, width, height, xlimits)
|
||||
|
||||
|
||||
def cubic_bezier(start, end, c1, c2):
|
||||
|
||||
def bezier_eq(p0, p1, p2, p3):
|
||||
|
||||
def f(t):
|
||||
tm1 = 1 - t
|
||||
tm1_3 = tm1 * tm1 * tm1
|
||||
t_3 = t * t * t
|
||||
return tm1_3 * p0 + 3 * t * tm1 * (tm1 * p1 + t * p2) + t_3 * p3
|
||||
return f
|
||||
|
||||
bezier_x = bezier_eq(start[0], c1[0], c2[0], end[0])
|
||||
bezier_y = bezier_eq(start[1], c1[1], c2[1], end[1])
|
||||
return bezier_x, bezier_y
|
||||
|
||||
|
||||
def find_bezier_for_D(width, height):
|
||||
cx = last_cx = width - 1
|
||||
start = (0, 0)
|
||||
end = (0, height - 1)
|
||||
while True:
|
||||
c1 = cx, start[1]
|
||||
c2 = cx, end[1]
|
||||
bezier_x, bezier_y = cubic_bezier(start, end, c1, c2)
|
||||
if bezier_x(0.5) > width - 1:
|
||||
return last_cx
|
||||
last_cx = cx
|
||||
cx += 1
|
||||
|
||||
|
||||
def get_bezier_limits(bezier_x, bezier_y):
|
||||
start_x = bezier_x(0)
|
||||
max_x = int(bezier_x(0.5))
|
||||
last_t, t_limit = 0, 0.5
|
||||
|
||||
def find_t_for_x(x, start_t):
|
||||
if abs(bezier_x(start_t) - x) < 0.1:
|
||||
return start_t
|
||||
increment = t_limit - start_t
|
||||
if increment <= 0:
|
||||
return start_t
|
||||
while True:
|
||||
q = bezier_x(start_t + increment)
|
||||
if (abs(q - x) < 0.1):
|
||||
return start_t + increment
|
||||
if q > x:
|
||||
increment /= 2
|
||||
if increment < 1e-6:
|
||||
raise ValueError('Failed to find t for x={}'.format(x))
|
||||
else:
|
||||
start_t += increment
|
||||
increment = t_limit - start_t
|
||||
if increment <= 0:
|
||||
return start_t
|
||||
|
||||
for x in range(start_x, max_x + 1):
|
||||
if x > start_x:
|
||||
last_t = find_t_for_x(x, last_t)
|
||||
upper, lower = bezier_y(last_t), bezier_y(1 - last_t)
|
||||
if abs(upper - lower) <= 2: # avoid pip on end of D
|
||||
break
|
||||
yield upper, lower
|
||||
|
||||
|
||||
def D(buf, width, height, left=True):
|
||||
c1x = find_bezier_for_D(width, height)
|
||||
start = (0, 0)
|
||||
end = (0, height - 1)
|
||||
c1 = c1x, start[1]
|
||||
c2 = c1x, end[1]
|
||||
bezier_x, bezier_y = cubic_bezier(start, end, c1, c2)
|
||||
xlimits = list(get_bezier_limits(bezier_x, bezier_y))
|
||||
if left:
|
||||
fill_region(buf, width, height, xlimits)
|
||||
else:
|
||||
mbuf = bytearray(width * height)
|
||||
fill_region(mbuf, width, height, xlimits)
|
||||
for y in range(height):
|
||||
offset = y * width
|
||||
for src_x in range(width):
|
||||
dest_x = width - 1 - src_x
|
||||
buf[offset + dest_x] = mbuf[offset + src_x]
|
||||
|
||||
|
||||
def half_dhline(buf, width, height, level=1, which='left', only=None):
|
||||
@@ -289,16 +376,42 @@ def hblock(buf, width, height, frac=1, gravity='left'):
|
||||
buf[c] = 255
|
||||
|
||||
|
||||
def shade(buf, width, height, frac=1/4):
|
||||
rand = bytearray(os.urandom(width * height))
|
||||
cutoff = int(frac * 255)
|
||||
def shade(buf, width, height, light=False, invert=False):
|
||||
square_sz = max(1, width // 12)
|
||||
number_of_rows = height // square_sz
|
||||
number_of_cols = width // square_sz
|
||||
nums = tuple(range(square_sz))
|
||||
|
||||
for r in range(height):
|
||||
off = width * r
|
||||
for c in range(width):
|
||||
q = off + c
|
||||
if rand[q] < cutoff:
|
||||
buf[q] = 255
|
||||
dest = bytearray(width * height) if invert else buf
|
||||
|
||||
for r in range(number_of_rows):
|
||||
y = r * square_sz
|
||||
is_odd = r % 2 != 0
|
||||
if is_odd:
|
||||
continue
|
||||
fill_even = r % 4 == 0
|
||||
for yr in nums:
|
||||
y = r * square_sz + yr
|
||||
if y >= height:
|
||||
break
|
||||
off = width * y
|
||||
for c in range(number_of_cols):
|
||||
if light:
|
||||
fill = (c % 4) == (0 if fill_even else 2)
|
||||
else:
|
||||
fill = (c % 2 == 0) == fill_even
|
||||
if fill:
|
||||
for xc in nums:
|
||||
x = (c * square_sz) + xc
|
||||
if x >= width:
|
||||
break
|
||||
dest[off + x] = 255
|
||||
if invert:
|
||||
for y in range(height):
|
||||
off = width * y
|
||||
for x in range(width):
|
||||
q = off + x
|
||||
buf[q] = 255 - dest[q]
|
||||
|
||||
|
||||
def quad(buf, width, height, x=0, y=0):
|
||||
@@ -345,6 +458,8 @@ box_chars = {
|
||||
'╿': [p(half_vline, level=3), p(half_vline, which='bottom')],
|
||||
'': [triangle],
|
||||
'': [p(triangle, left=False)],
|
||||
'': [D],
|
||||
'': [p(D, left=False)],
|
||||
'═': [dhline],
|
||||
'║': [dvline],
|
||||
'╞': [vline, p(half_dhline, which='right')],
|
||||
@@ -375,9 +490,9 @@ box_chars = {
|
||||
'▎': [p(hblock, frac=1/4)],
|
||||
'▏': [p(hblock, frac=1/8)],
|
||||
'▐': [p(hblock, frac=1/2, gravity='right')],
|
||||
'░': [p(shade, frac=1/4)],
|
||||
'▒': [p(shade, frac=2/4)],
|
||||
'▓': [p(shade, frac=3/4)],
|
||||
'░': [p(shade, light=True)],
|
||||
'▒': [shade],
|
||||
'▓': [p(shade, invert=True)],
|
||||
'▔': [p(vblock, frac=1/8)],
|
||||
'▕': [p(hblock, frac=1/8, gravity='right')],
|
||||
'▖': [p(quad, y=1)],
|
||||
@@ -434,6 +549,26 @@ def render_missing_glyph(buf, width, height):
|
||||
draw_vline(buf, width, vgap, height - vgap + 1, width - hgap, 0)
|
||||
|
||||
|
||||
def test_char(ch, sz=48):
|
||||
# kitty +runpy "from kitty.fonts.box_drawing import test_char; import sys; test_char('XXX')"
|
||||
from .render import display_bitmap, setup_for_testing
|
||||
from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu
|
||||
width, height = setup_for_testing('monospace', sz)[1:]
|
||||
buf = bytearray(width * height)
|
||||
try:
|
||||
render_box_char(ch, buf, width, height)
|
||||
|
||||
def join_cells(*cells):
|
||||
cells = tuple(bytes(x) for x in cells)
|
||||
return concat_cells(width, height, False, cells)
|
||||
|
||||
rgb_data = join_cells(buf)
|
||||
display_bitmap(rgb_data, width, height)
|
||||
print()
|
||||
finally:
|
||||
set_send_sprite_to_gpu(None)
|
||||
|
||||
|
||||
def test_drawing(sz=48, family='monospace'):
|
||||
from .render import display_bitmap, setup_for_testing
|
||||
from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu
|
||||
|
||||
@@ -220,7 +220,7 @@ def shape_string(text="abcd", family='monospace', size=11.0, dpi=96.0, path=None
|
||||
|
||||
def display_bitmap(rgb_data, width, height):
|
||||
from tempfile import NamedTemporaryFile
|
||||
from kitty.icat import detect_support, show
|
||||
from kittens.icat.main import detect_support, show
|
||||
if not hasattr(display_bitmap, 'detected') and not detect_support():
|
||||
raise SystemExit('Your terminal does not support the graphics protocol')
|
||||
display_bitmap.detected = True
|
||||
|
||||
@@ -214,15 +214,20 @@ ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_dat
|
||||
|
||||
PyObject*
|
||||
face_from_descriptor(PyObject *descriptor) {
|
||||
#define D(key, conv) { PyObject *t = PyDict_GetItemString(descriptor, #key); if (t == NULL) return NULL; key = conv(t); t = NULL; }
|
||||
char *path;
|
||||
long index;
|
||||
bool hinting;
|
||||
long hint_style;
|
||||
D(path, PyUnicode_AsUTF8);
|
||||
D(index, PyLong_AsLong);
|
||||
D(hinting, PyObject_IsTrue);
|
||||
D(hint_style, PyLong_AsLong);
|
||||
#define D(key, conv, missing_ok) { \
|
||||
PyObject *t = PyDict_GetItemString(descriptor, #key); \
|
||||
if (t == NULL) { \
|
||||
if (!missing_ok) { PyErr_SetString(PyExc_KeyError, "font descriptor is missing the key: " #key); return NULL; } \
|
||||
} else key = conv(t); \
|
||||
}
|
||||
char *path = NULL;
|
||||
long index = 0;
|
||||
bool hinting = false;
|
||||
long hint_style = 0;
|
||||
D(path, PyUnicode_AsUTF8, false);
|
||||
D(index, PyLong_AsLong, true);
|
||||
D(hinting, PyObject_IsTrue, true);
|
||||
D(hint_style, PyLong_AsLong, true);
|
||||
#undef D
|
||||
Face *self = (Face *)Face_Type.tp_alloc(&Face_Type, 0);
|
||||
if (self != NULL) {
|
||||
@@ -255,10 +260,11 @@ dealloc(Face* self) {
|
||||
|
||||
static PyObject *
|
||||
repr(Face *self) {
|
||||
const char *ps_name = FT_Get_Postscript_Name(self->face);
|
||||
return PyUnicode_FromFormat(
|
||||
"Face(family=%s, style=%s, ps_name=%s, path=%S, index=%d, is_scalable=%S, has_color=%S, ascender=%i, descender=%i, height=%i, underline_position=%i, underline_thickness=%i)",
|
||||
self->face->family_name ? self->face->family_name : "", self->face->style_name ? self->face->style_name : "",
|
||||
FT_Get_Postscript_Name(self->face),
|
||||
ps_name ? ps_name: "",
|
||||
self->path, self->index, self->is_scalable ? Py_True : Py_False, self->has_color ? Py_True : Py_False,
|
||||
self->ascender, self->descender, self->height, self->underline_position, self->underline_thickness
|
||||
);
|
||||
|
||||
6265
kitty/gl-wrapper.c
generated
6265
kitty/gl-wrapper.c
generated
File diff suppressed because it is too large
Load Diff
43
kitty/gl-wrapper.h
generated
43
kitty/gl-wrapper.h
generated
@@ -1,7 +1,5 @@
|
||||
/*
|
||||
|
||||
OpenGL loader generated by glad 0.1.15a0 on Wed Nov 8 14:58:53 2017.
|
||||
|
||||
OpenGL loader generated by glad 0.1.20a0 on Fri May 4 18:11:55 2018.
|
||||
Language/Generator: C/C++ Debug
|
||||
Specification: gl
|
||||
APIs: gl=3.3
|
||||
@@ -15,22 +13,17 @@
|
||||
Loader: True
|
||||
Local files: False
|
||||
Omit khrplatform: False
|
||||
|
||||
Commandline:
|
||||
--profile="core" --api="gl=3.3" --generator="c-debug" --spec="gl" --extensions="GL_ARB_copy_image,GL_ARB_multisample,GL_ARB_robustness,GL_ARB_texture_storage,GL_KHR_debug"
|
||||
Online:
|
||||
http://glad.dav1d.de/#profile=core&language=c-debug&specification=gl&loader=on&api=gl%3D3.3&extensions=GL_ARB_copy_image&extensions=GL_ARB_multisample&extensions=GL_ARB_robustness&extensions=GL_ARB_texture_storage&extensions=GL_KHR_debug
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __glad_h_
|
||||
#define __glad_h_
|
||||
|
||||
#ifdef __gl_h_
|
||||
#error OpenGL header already included, remove this include, glad already provides it
|
||||
#endif
|
||||
#define __gl_h_
|
||||
|
||||
#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
@@ -40,25 +33,20 @@
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifndef APIENTRY
|
||||
#define APIENTRY
|
||||
#endif
|
||||
#ifndef APIENTRYP
|
||||
#define APIENTRYP APIENTRY *
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct gladGLversionStruct {
|
||||
int major;
|
||||
int minor;
|
||||
};
|
||||
|
||||
typedef void* (* GLADloadproc)(const char *name);
|
||||
|
||||
#ifndef GLAPI
|
||||
# if defined(GLAD_GLAPI_EXPORT)
|
||||
# if defined(_WIN32) || defined(__CYGWIN__)
|
||||
@@ -84,17 +72,11 @@ typedef void* (* GLADloadproc)(const char *name);
|
||||
# define GLAPI extern
|
||||
# endif
|
||||
#endif
|
||||
|
||||
GLAPI struct gladGLversionStruct GLVersion;
|
||||
|
||||
GLAPI int gladLoadGL(void);
|
||||
|
||||
GLAPI int gladLoadGLLoader(GLADloadproc);
|
||||
|
||||
|
||||
#define GLAD_DEBUG
|
||||
typedef void (* GLADcallback)(const char *name, void *funcptr, int len_args, ...);
|
||||
|
||||
GLAPI void glad_set_pre_callback(GLADcallback cb);
|
||||
GLAPI void glad_set_post_callback(GLADcallback cb);
|
||||
#include <stddef.h>
|
||||
@@ -1112,11 +1094,11 @@ typedef void (APIENTRYP PFNGLENABLEPROC)(GLenum cap);
|
||||
GLAPI PFNGLENABLEPROC glad_glEnable;
|
||||
GLAPI PFNGLENABLEPROC glad_debug_glEnable;
|
||||
#define glEnable glad_debug_glEnable
|
||||
typedef void (APIENTRYP PFNGLFINISHPROC)();
|
||||
typedef void (APIENTRYP PFNGLFINISHPROC)(void);
|
||||
GLAPI PFNGLFINISHPROC glad_glFinish;
|
||||
GLAPI PFNGLFINISHPROC glad_debug_glFinish;
|
||||
#define glFinish glad_debug_glFinish
|
||||
typedef void (APIENTRYP PFNGLFLUSHPROC)();
|
||||
typedef void (APIENTRYP PFNGLFLUSHPROC)(void);
|
||||
GLAPI PFNGLFLUSHPROC glad_glFlush;
|
||||
GLAPI PFNGLFLUSHPROC glad_debug_glFlush;
|
||||
#define glFlush glad_debug_glFlush
|
||||
@@ -1164,7 +1146,7 @@ typedef void (APIENTRYP PFNGLGETDOUBLEVPROC)(GLenum pname, GLdouble *data);
|
||||
GLAPI PFNGLGETDOUBLEVPROC glad_glGetDoublev;
|
||||
GLAPI PFNGLGETDOUBLEVPROC glad_debug_glGetDoublev;
|
||||
#define glGetDoublev glad_debug_glGetDoublev
|
||||
typedef GLenum (APIENTRYP PFNGLGETERRORPROC)();
|
||||
typedef GLenum (APIENTRYP PFNGLGETERRORPROC)(void);
|
||||
GLAPI PFNGLGETERRORPROC glad_glGetError;
|
||||
GLAPI PFNGLGETERRORPROC glad_debug_glGetError;
|
||||
#define glGetError glad_debug_glGetError
|
||||
@@ -1204,7 +1186,7 @@ typedef GLboolean (APIENTRYP PFNGLISENABLEDPROC)(GLenum cap);
|
||||
GLAPI PFNGLISENABLEDPROC glad_glIsEnabled;
|
||||
GLAPI PFNGLISENABLEDPROC glad_debug_glIsEnabled;
|
||||
#define glIsEnabled glad_debug_glIsEnabled
|
||||
typedef void (APIENTRYP PFNGLDEPTHRANGEPROC)(GLdouble near, GLdouble far);
|
||||
typedef void (APIENTRYP PFNGLDEPTHRANGEPROC)(GLdouble n, GLdouble f);
|
||||
GLAPI PFNGLDEPTHRANGEPROC glad_glDepthRange;
|
||||
GLAPI PFNGLDEPTHRANGEPROC glad_debug_glDepthRange;
|
||||
#define glDepthRange glad_debug_glDepthRange
|
||||
@@ -1484,7 +1466,7 @@ typedef void (APIENTRYP PFNGLCOMPILESHADERPROC)(GLuint shader);
|
||||
GLAPI PFNGLCOMPILESHADERPROC glad_glCompileShader;
|
||||
GLAPI PFNGLCOMPILESHADERPROC glad_debug_glCompileShader;
|
||||
#define glCompileShader glad_debug_glCompileShader
|
||||
typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC)();
|
||||
typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC)(void);
|
||||
GLAPI PFNGLCREATEPROGRAMPROC glad_glCreateProgram;
|
||||
GLAPI PFNGLCREATEPROGRAMPROC glad_debug_glCreateProgram;
|
||||
#define glCreateProgram glad_debug_glCreateProgram
|
||||
@@ -1884,7 +1866,7 @@ typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKPROC)(GLenum primitiveMode);
|
||||
GLAPI PFNGLBEGINTRANSFORMFEEDBACKPROC glad_glBeginTransformFeedback;
|
||||
GLAPI PFNGLBEGINTRANSFORMFEEDBACKPROC glad_debug_glBeginTransformFeedback;
|
||||
#define glBeginTransformFeedback glad_debug_glBeginTransformFeedback
|
||||
typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKPROC)();
|
||||
typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKPROC)(void);
|
||||
GLAPI PFNGLENDTRANSFORMFEEDBACKPROC glad_glEndTransformFeedback;
|
||||
GLAPI PFNGLENDTRANSFORMFEEDBACKPROC glad_debug_glEndTransformFeedback;
|
||||
#define glEndTransformFeedback glad_debug_glEndTransformFeedback
|
||||
@@ -1912,7 +1894,7 @@ typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERPROC)(GLuint id, GLenum mode)
|
||||
GLAPI PFNGLBEGINCONDITIONALRENDERPROC glad_glBeginConditionalRender;
|
||||
GLAPI PFNGLBEGINCONDITIONALRENDERPROC glad_debug_glBeginConditionalRender;
|
||||
#define glBeginConditionalRender glad_debug_glBeginConditionalRender
|
||||
typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERPROC)();
|
||||
typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERPROC)(void);
|
||||
GLAPI PFNGLENDCONDITIONALRENDERPROC glad_glEndConditionalRender;
|
||||
GLAPI PFNGLENDCONDITIONALRENDERPROC glad_debug_glEndConditionalRender;
|
||||
#define glEndConditionalRender glad_debug_glEndConditionalRender
|
||||
@@ -2678,7 +2660,7 @@ GLAPI PFNGLSAMPLECOVERAGEARBPROC glad_debug_glSampleCoverageARB;
|
||||
#ifndef GL_ARB_robustness
|
||||
#define GL_ARB_robustness 1
|
||||
GLAPI int GLAD_GL_ARB_robustness;
|
||||
typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSARBPROC)();
|
||||
typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSARBPROC)(void);
|
||||
GLAPI PFNGLGETGRAPHICSRESETSTATUSARBPROC glad_glGetGraphicsResetStatusARB;
|
||||
GLAPI PFNGLGETGRAPHICSRESETSTATUSARBPROC glad_debug_glGetGraphicsResetStatusARB;
|
||||
#define glGetGraphicsResetStatusARB glad_debug_glGetGraphicsResetStatusARB
|
||||
@@ -2798,7 +2780,7 @@ typedef void (APIENTRYP PFNGLPUSHDEBUGGROUPPROC)(GLenum source, GLuint id, GLsiz
|
||||
GLAPI PFNGLPUSHDEBUGGROUPPROC glad_glPushDebugGroup;
|
||||
GLAPI PFNGLPUSHDEBUGGROUPPROC glad_debug_glPushDebugGroup;
|
||||
#define glPushDebugGroup glad_debug_glPushDebugGroup
|
||||
typedef void (APIENTRYP PFNGLPOPDEBUGGROUPPROC)();
|
||||
typedef void (APIENTRYP PFNGLPOPDEBUGGROUPPROC)(void);
|
||||
GLAPI PFNGLPOPDEBUGGROUPPROC glad_glPopDebugGroup;
|
||||
GLAPI PFNGLPOPDEBUGGROUPPROC glad_debug_glPopDebugGroup;
|
||||
#define glPopDebugGroup glad_debug_glPopDebugGroup
|
||||
@@ -2842,7 +2824,7 @@ typedef void (APIENTRYP PFNGLPUSHDEBUGGROUPKHRPROC)(GLenum source, GLuint id, GL
|
||||
GLAPI PFNGLPUSHDEBUGGROUPKHRPROC glad_glPushDebugGroupKHR;
|
||||
GLAPI PFNGLPUSHDEBUGGROUPKHRPROC glad_debug_glPushDebugGroupKHR;
|
||||
#define glPushDebugGroupKHR glad_debug_glPushDebugGroupKHR
|
||||
typedef void (APIENTRYP PFNGLPOPDEBUGGROUPKHRPROC)();
|
||||
typedef void (APIENTRYP PFNGLPOPDEBUGGROUPKHRPROC)(void);
|
||||
GLAPI PFNGLPOPDEBUGGROUPKHRPROC glad_glPopDebugGroupKHR;
|
||||
GLAPI PFNGLPOPDEBUGGROUPKHRPROC glad_debug_glPopDebugGroupKHR;
|
||||
#define glPopDebugGroupKHR glad_debug_glPopDebugGroupKHR
|
||||
@@ -2867,11 +2849,8 @@ GLAPI PFNGLGETPOINTERVKHRPROC glad_glGetPointervKHR;
|
||||
GLAPI PFNGLGETPOINTERVKHRPROC glad_debug_glGetPointervKHR;
|
||||
#define glGetPointervKHR glad_debug_glGetPointervKHR
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
int init_glad(GLADloadproc, int);
|
||||
|
||||
@@ -59,6 +59,7 @@ gl_init() {
|
||||
ARB_TEST(texture_storage);
|
||||
#undef ARB_TEST
|
||||
glad_loaded = true;
|
||||
if (global_state.debug_gl) printf("GL version string: '%s' Detected version: %d.%d\n", glGetString(GL_VERSION), GLVersion.major, GLVersion.minor);
|
||||
if (GLVersion.major < OPENGL_REQUIRED_VERSION_MAJOR || (GLVersion.major == OPENGL_REQUIRED_VERSION_MAJOR && GLVersion.minor < OPENGL_REQUIRED_VERSION_MINOR)) {
|
||||
fatal("OpenGL version is %d.%d, version >= 3.3 required for kitty", GLVersion.major, GLVersion.minor);
|
||||
}
|
||||
|
||||
2
kitty/glfw-wrapper.c
generated
2
kitty/glfw-wrapper.c
generated
@@ -357,6 +357,8 @@ load_glfw(const char* path) {
|
||||
|
||||
*(void **) (&glfwGetCocoaMonitor_impl) = dlsym(handle, "glfwGetCocoaMonitor");
|
||||
|
||||
*(void **) (&glfwSetCocoaTextInputFilter_impl) = dlsym(handle, "glfwSetCocoaTextInputFilter");
|
||||
|
||||
*(void **) (&glfwGetX11Display_impl) = dlsym(handle, "glfwGetX11Display");
|
||||
|
||||
*(void **) (&glfwGetX11Window_impl) = dlsym(handle, "glfwGetX11Window");
|
||||
|
||||
5
kitty/glfw-wrapper.h
generated
5
kitty/glfw-wrapper.h
generated
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
||||
|
||||
|
||||
/*! @name GLFW version macros
|
||||
@@ -1825,6 +1826,10 @@ typedef uint32_t (*glfwGetCocoaMonitor_func)(GLFWmonitor*);
|
||||
glfwGetCocoaMonitor_func glfwGetCocoaMonitor_impl;
|
||||
#define glfwGetCocoaMonitor glfwGetCocoaMonitor_impl
|
||||
|
||||
typedef GLFWcocoatextinputfilterfun (*glfwSetCocoaTextInputFilter_func)(GLFWwindow*, GLFWcocoatextinputfilterfun);
|
||||
glfwSetCocoaTextInputFilter_func glfwSetCocoaTextInputFilter_impl;
|
||||
#define glfwSetCocoaTextInputFilter glfwSetCocoaTextInputFilter_impl
|
||||
|
||||
typedef void* (*glfwGetX11Display_func)();
|
||||
glfwGetX11Display_func glfwGetX11Display_impl;
|
||||
#define glfwGetX11Display glfwGetX11Display_impl
|
||||
|
||||
40
kitty/glfw.c
40
kitty/glfw.c
@@ -9,7 +9,8 @@
|
||||
#include "glfw-wrapper.h"
|
||||
extern bool cocoa_make_window_resizable(void *w);
|
||||
extern void cocoa_create_global_menu(void);
|
||||
extern void cocoa_set_titlebar_color(void *w);
|
||||
extern void cocoa_set_hide_from_tasks(void);
|
||||
extern void cocoa_set_titlebar_color(void *w, color_type color);
|
||||
|
||||
#if GLFW_KEY_LAST >= MAX_KEY_COUNT
|
||||
#error "glfw has too many keys, you should increase MAX_KEY_COUNT"
|
||||
@@ -309,12 +310,29 @@ set_dpi_from_os_window(OSWindow *w) {
|
||||
|
||||
static bool is_first_window = true;
|
||||
|
||||
#ifdef __APPLE__
|
||||
static int
|
||||
filter_option(int key UNUSED, int mods, unsigned int scancode UNUSED) {
|
||||
return ((mods == GLFW_MOD_ALT) || (mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT))) ? 1 : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
set_titlebar_color(OSWindow *w, color_type color) {
|
||||
if (w->handle && (!w->last_titlebar_color || (w->last_titlebar_color & 0xffffff) != (color & 0xffffff))) {
|
||||
w->last_titlebar_color = (1 << 24) | (color & 0xffffff);
|
||||
#ifdef __APPLE__
|
||||
cocoa_set_titlebar_color(glfwGetCocoaWindow(w->handle), color);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
int width, height, x = -1, y = -1;
|
||||
char *title, *wm_class_class, *wm_class_name;
|
||||
PyObject *load_programs = NULL;
|
||||
if (!PyArg_ParseTuple(args, "iisss|Oiii", &width, &height, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "iisss|Oii", &width, &height, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y)) return NULL;
|
||||
|
||||
if (is_first_window) {
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
@@ -367,6 +385,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
Py_DECREF(ret);
|
||||
#ifdef __APPLE__
|
||||
cocoa_create_global_menu();
|
||||
if (OPT(macos_option_as_alt)) glfwSetCocoaTextInputFilter(glfw_window, filter_option);
|
||||
// This needs to be done only after the first window has been created, because glfw only sets the activation policy once upon initialization.
|
||||
if (OPT(macos_hide_from_tasks)) cocoa_set_hide_from_tasks();
|
||||
#endif
|
||||
is_first_window = false;
|
||||
}
|
||||
@@ -395,7 +416,6 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
if (glfwGetCocoaWindow) { if (!cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window))) { PyErr_Print(); } }
|
||||
else log_error("Failed to load glfwGetCocoaWindow");
|
||||
}
|
||||
cocoa_set_titlebar_color(glfwGetCocoaWindow(glfw_window));
|
||||
#endif
|
||||
double now = monotonic();
|
||||
w->is_focused = true;
|
||||
@@ -609,7 +629,7 @@ request_window_attention(id_type kitty_window_id, bool audio_bell) {
|
||||
OSWindow *w = os_window_for_kitty_window(kitty_window_id);
|
||||
if (w) {
|
||||
if (audio_bell) ring_audio_bell(w);
|
||||
glfwRequestWindowAttention(w->handle);
|
||||
if (OPT(window_alert_on_bell)) glfwRequestWindowAttention(w->handle);
|
||||
glfwPostEmptyEvent();
|
||||
}
|
||||
}
|
||||
@@ -745,6 +765,16 @@ os_window_swap_buffers(PyObject UNUSED *self, PyObject *args) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
ring_bell(PyObject UNUSED *self, PyObject *args) {
|
||||
id_type os_window_id;
|
||||
if (!PyArg_ParseTuple(args, "K", &os_window_id)) return NULL;
|
||||
OSWindow *w = os_window_for_kitty_window(os_window_id);
|
||||
if (w && w->handle) {
|
||||
glfwWindowBell(w->handle);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
// Boilerplate {{{
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
@@ -758,6 +788,7 @@ static PyMethodDef module_methods[] = {
|
||||
METHODB(glfw_window_hint, METH_VARARGS),
|
||||
METHODB(os_window_should_close, METH_VARARGS),
|
||||
METHODB(os_window_swap_buffers, METH_VARARGS),
|
||||
METHODB(ring_bell, METH_VARARGS),
|
||||
METHODB(get_primary_selection, METH_NOARGS),
|
||||
METHODB(x11_display, METH_NOARGS),
|
||||
METHODB(x11_window_id, METH_O),
|
||||
@@ -927,6 +958,7 @@ init_glfw(PyObject *m) {
|
||||
ADDC(GLFW_MOD_CONTROL);
|
||||
ADDC(GLFW_MOD_ALT);
|
||||
ADDC(GLFW_MOD_SUPER);
|
||||
ADDC(GLFW_MOD_KITTY);
|
||||
|
||||
// --- Mouse -------------------------------------------------------------------
|
||||
ADDC(GLFW_MOUSE_BUTTON_1);
|
||||
|
||||
@@ -873,9 +873,10 @@ new(PyTypeObject UNUSED *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
|
||||
static inline PyObject*
|
||||
image_as_dict(Image *img) {
|
||||
#define U(x) #x, img->x
|
||||
return Py_BuildValue("{sI sI sI sI sI sI sH sH sN}",
|
||||
U(texture_id), U(client_id), U(width), U(height), U(internal_id), U(refcnt), U(data_loaded),
|
||||
"is_4byte_aligned", img->load_data.is_4byte_aligned,
|
||||
return Py_BuildValue("{sI sI sI sI sI sI sO sO sN}",
|
||||
U(texture_id), U(client_id), U(width), U(height), U(internal_id), U(refcnt),
|
||||
"data_loaded", img->data_loaded ? Py_True : Py_False,
|
||||
"is_4byte_aligned", img->load_data.is_4byte_aligned ? Py_True : Py_False,
|
||||
"data", Py_BuildValue("y#", img->load_data.data, img->load_data.data_sz)
|
||||
);
|
||||
#undef U
|
||||
|
||||
125
kitty/history.c
125
kitty/history.c
@@ -10,10 +10,42 @@
|
||||
#include <structmember.h>
|
||||
|
||||
extern PyTypeObject Line_Type;
|
||||
#define SEGMENT_SIZE 2048
|
||||
|
||||
static inline void
|
||||
add_segment(HistoryBuf *self) {
|
||||
self->num_segments += 1;
|
||||
self->segments = PyMem_Realloc(self->segments, sizeof(HistoryBufSegment) * self->num_segments);
|
||||
if (self->segments == NULL) fatal("Out of memory allocating new history buffer segment");
|
||||
HistoryBufSegment *s = self->segments + self->num_segments - 1;
|
||||
s->cells = PyMem_Calloc(self->xnum * SEGMENT_SIZE, sizeof(Cell));
|
||||
s->line_attrs = PyMem_Calloc(SEGMENT_SIZE, sizeof(line_attrs_type));
|
||||
if (s->cells == NULL || s->line_attrs == NULL) fatal("Out of memory allocating new history buffer segment");
|
||||
}
|
||||
|
||||
static inline index_type
|
||||
segment_for(HistoryBuf *self, index_type y) {
|
||||
index_type seg_num = y / SEGMENT_SIZE;
|
||||
while (UNLIKELY(seg_num >= self->num_segments && SEGMENT_SIZE * self->num_segments < self->ynum)) add_segment(self);
|
||||
if (UNLIKELY(seg_num >= self->num_segments)) fatal("Out of bounds access to history buffer line number: %u", y);
|
||||
return seg_num;
|
||||
}
|
||||
|
||||
#define seg_ptr(which, stride) { \
|
||||
index_type seg_num = segment_for(self, y); \
|
||||
y -= seg_num * SEGMENT_SIZE; \
|
||||
return self->segments[seg_num].which + y * stride; \
|
||||
}
|
||||
|
||||
static inline Cell*
|
||||
lineptr(HistoryBuf *linebuf, index_type y) {
|
||||
return linebuf->buf + y * linebuf->xnum;
|
||||
lineptr(HistoryBuf *self, index_type y) {
|
||||
seg_ptr(cells, self->xnum);
|
||||
}
|
||||
|
||||
|
||||
static inline line_attrs_type*
|
||||
attrptr(HistoryBuf *self, index_type y) {
|
||||
seg_ptr(line_attrs, 1);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
@@ -32,18 +64,12 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
if (self != NULL) {
|
||||
self->xnum = xnum;
|
||||
self->ynum = ynum;
|
||||
self->buf = PyMem_Calloc(xnum * ynum, sizeof(Cell));
|
||||
self->line_attrs = PyMem_Calloc(ynum, sizeof(line_attrs_type));
|
||||
self->line = alloc_line();
|
||||
if (self->buf == NULL || self->line == NULL || self->line_attrs == NULL) {
|
||||
PyErr_NoMemory();
|
||||
PyMem_Free(self->buf); Py_CLEAR(self->line); PyMem_Free(self->line_attrs);
|
||||
Py_CLEAR(self);
|
||||
} else {
|
||||
self->line->xnum = xnum;
|
||||
for(index_type y = 0; y < self->ynum; y++) {
|
||||
clear_chars_in_line(lineptr(self, y), self->xnum, BLANK_CHAR);
|
||||
}
|
||||
self->num_segments = 0;
|
||||
add_segment(self);
|
||||
self->line->xnum = xnum;
|
||||
for(index_type y = 0; y < self->ynum; y++) {
|
||||
clear_chars_in_line(lineptr(self, y), self->xnum, BLANK_CHAR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +79,11 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
static void
|
||||
dealloc(HistoryBuf* self) {
|
||||
Py_CLEAR(self->line);
|
||||
PyMem_Free(self->buf);
|
||||
PyMem_Free(self->line_attrs);
|
||||
for (size_t i = 0; i < self->num_segments; i++) {
|
||||
PyMem_Free(self->segments[i].cells);
|
||||
PyMem_Free(self->segments[i].line_attrs);
|
||||
}
|
||||
PyMem_Free(self->segments);
|
||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||
}
|
||||
|
||||
@@ -71,8 +100,8 @@ static inline void
|
||||
init_line(HistoryBuf *self, index_type num, Line *l) {
|
||||
// Initialize the line l, setting its pointer to the offsets for the line at index (buffer position) num
|
||||
l->cells = lineptr(self, num);
|
||||
l->continued = self->line_attrs[num] & CONTINUED_MASK;
|
||||
l->has_dirty_text = self->line_attrs[num] & TEXT_DIRTY_MASK ? true : false;
|
||||
l->continued = *attrptr(self, num) & CONTINUED_MASK;
|
||||
l->has_dirty_text = *attrptr(self, num) & TEXT_DIRTY_MASK ? true : false;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -82,12 +111,14 @@ historybuf_init_line(HistoryBuf *self, index_type lnum, Line *l) {
|
||||
|
||||
void
|
||||
historybuf_mark_line_clean(HistoryBuf *self, index_type y) {
|
||||
self->line_attrs[index_of(self, y)] &= ~TEXT_DIRTY_MASK;
|
||||
line_attrs_type *p = attrptr(self, index_of(self, y));
|
||||
*p &= ~TEXT_DIRTY_MASK;
|
||||
}
|
||||
|
||||
void
|
||||
historybuf_mark_line_dirty(HistoryBuf *self, index_type y) {
|
||||
self->line_attrs[index_of(self, y)] |= TEXT_DIRTY_MASK;
|
||||
line_attrs_type *p = attrptr(self, index_of(self, y));
|
||||
*p |= TEXT_DIRTY_MASK;
|
||||
}
|
||||
|
||||
inline void
|
||||
@@ -105,43 +136,11 @@ historybuf_push(HistoryBuf *self) {
|
||||
return idx;
|
||||
}
|
||||
|
||||
bool
|
||||
historybuf_resize(HistoryBuf *self, index_type lines) {
|
||||
HistoryBuf t = {{0}};
|
||||
t.xnum=self->xnum;
|
||||
t.ynum=lines;
|
||||
if (t.ynum > 0 && t.ynum != self->ynum) {
|
||||
t.buf = PyMem_Calloc(t.xnum * t.ynum, sizeof(Cell));
|
||||
if (t.buf == NULL) { PyErr_NoMemory(); return false; }
|
||||
t.line_attrs = PyMem_Calloc(t.ynum, sizeof(line_attrs_type));
|
||||
if (t.line_attrs == NULL) { PyMem_Free(t.buf); PyErr_NoMemory(); return false; }
|
||||
t.count = MIN(self->count, t.ynum);
|
||||
for (index_type s=0; s < t.count; s++) {
|
||||
index_type si = index_of(self, s), ti = index_of(&t, s);
|
||||
copy_cells(lineptr(self, si), lineptr(&t, ti), t.xnum);
|
||||
t.line_attrs[ti] = self->line_attrs[si];
|
||||
}
|
||||
self->count = t.count;
|
||||
self->start_of_data = t.start_of_data;
|
||||
self->ynum = t.ynum;
|
||||
PyMem_Free(self->buf); PyMem_Free(self->line_attrs);
|
||||
self->buf = t.buf; self->line_attrs = t.line_attrs;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
historybuf_add_line(HistoryBuf *self, const Line *line) {
|
||||
index_type idx = historybuf_push(self);
|
||||
copy_line(line, self->line);
|
||||
self->line_attrs[idx] = (line->continued & CONTINUED_MASK) | (line->has_dirty_text ? TEXT_DIRTY_MASK : 0);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
change_num_of_lines(HistoryBuf *self, PyObject *val) {
|
||||
#define change_num_of_lines_doc "Change the number of lines in this buffer"
|
||||
if(!historybuf_resize(self, (index_type)PyLong_AsUnsignedLong(val))) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
*attrptr(self, idx) = (line->continued & CONTINUED_MASK) | (line->has_dirty_text ? TEXT_DIRTY_MASK : 0);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
@@ -188,7 +187,7 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
|
||||
for(unsigned int i = 0; i < self->count; i++) {
|
||||
init_line(self, i, &l);
|
||||
if (i < self->count - 1) {
|
||||
l.continued = self->line_attrs[index_of(self, i + 1)] & CONTINUED_MASK;
|
||||
l.continued = *attrptr(self, index_of(self, i + 1)) & CONTINUED_MASK;
|
||||
} else l.continued = false;
|
||||
index_type num = line_as_ansi(&l, t, 5120);
|
||||
if (!(l.continued) && num < 5119) t[num++] = 10; // 10 = \n
|
||||
@@ -209,7 +208,7 @@ static PyObject*
|
||||
as_text(HistoryBuf *self, PyObject *args) {
|
||||
Line l = {.xnum=self->xnum};
|
||||
#define gl(self, y) get_line(self, y, &l);
|
||||
as_text_generic(args, self, gl, self->count, self->xnum, callback, as_ansi);
|
||||
as_text_generic(args, self, gl, self->count, self->xnum);
|
||||
#undef gl
|
||||
}
|
||||
|
||||
@@ -219,7 +218,7 @@ dirty_lines(HistoryBuf *self, PyObject *a UNUSED) {
|
||||
#define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text."
|
||||
PyObject *ans = PyList_New(0);
|
||||
for (index_type i = 0; i < self->ynum; i++) {
|
||||
if (self->line_attrs[i] & TEXT_DIRTY_MASK) {
|
||||
if (*attrptr(self, i) & TEXT_DIRTY_MASK) {
|
||||
PyList_Append(ans, PyLong_FromUnsignedLong(i));
|
||||
}
|
||||
}
|
||||
@@ -232,7 +231,6 @@ static PyObject* rewrap(HistoryBuf *self, PyObject *args);
|
||||
#define rewrap_doc ""
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
METHOD(change_num_of_lines, METH_O)
|
||||
METHOD(line, METH_O)
|
||||
METHOD(as_ansi, METH_O)
|
||||
METHODB(as_text, METH_VARARGS),
|
||||
@@ -275,19 +273,22 @@ HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns) {
|
||||
|
||||
#define init_src_line(src_y) init_line(src, map_src_index(src_y), src->line);
|
||||
|
||||
#define is_src_line_continued(src_y) (map_src_index(src_y) < src->ynum - 1 ? (src->line_attrs[map_src_index(src_y + 1)] & CONTINUED_MASK) : false)
|
||||
#define is_src_line_continued(src_y) (map_src_index(src_y) < src->ynum - 1 ? (*attrptr(src, map_src_index(src_y + 1)) & CONTINUED_MASK) : false)
|
||||
|
||||
#define next_dest_line(cont) dest->line_attrs[historybuf_push(dest)] = cont & CONTINUED_MASK; dest->line->continued = cont;
|
||||
#define next_dest_line(cont) *attrptr(dest, historybuf_push(dest)) = cont & CONTINUED_MASK; dest->line->continued = cont;
|
||||
|
||||
#define first_dest_line next_dest_line(false);
|
||||
|
||||
#include "rewrap.h"
|
||||
|
||||
void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) {
|
||||
// Fast path
|
||||
while(other->num_segments < self->num_segments) add_segment(other);
|
||||
if (other->xnum == self->xnum && other->ynum == self->ynum) {
|
||||
memcpy(other->buf, self->buf, sizeof(Cell) * self->xnum * self->ynum);
|
||||
memcpy(other->line_attrs, self->line_attrs, sizeof(line_attrs_type) * self->ynum);
|
||||
// Fast path
|
||||
for (index_type i = 0; i < self->num_segments; i++) {
|
||||
memcpy(other->segments[i].cells, self->segments[i].cells, SEGMENT_SIZE * self->xnum * sizeof(Cell));
|
||||
memcpy(other->segments[i].line_attrs, self->segments[i].line_attrs, SEGMENT_SIZE * sizeof(line_attrs_type));
|
||||
}
|
||||
other->count = self->count; other->start_of_data = self->start_of_data;
|
||||
return;
|
||||
}
|
||||
@@ -295,7 +296,7 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) {
|
||||
index_type x = 0, y = 0;
|
||||
if (self->count > 0) {
|
||||
rewrap_inner(self, other, self->count, NULL, &x, &y);
|
||||
for (index_type i = 0; i < other->count; i++) other->line_attrs[(other->start_of_data + i) % other->ynum] |= TEXT_DIRTY_MASK;
|
||||
for (index_type i = 0; i < other->count; i++) *attrptr(other, (other->start_of_data + i) % other->ynum) |= TEXT_DIRTY_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
26
kitty/keys.c
26
kitty/keys.c
@@ -43,22 +43,6 @@ active_window() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
on_text_input(unsigned int codepoint, int mods) {
|
||||
Window *w = active_window();
|
||||
static char buf[16];
|
||||
unsigned int sz = 0;
|
||||
|
||||
if (w != NULL) {
|
||||
bool is_text = mods <= GLFW_MOD_SHIFT;
|
||||
if (is_text) sz = encode_utf8(codepoint, buf);
|
||||
#ifdef __APPLE__
|
||||
if (!OPT(macos_option_as_alt) && IS_ALT_MODS(mods)) sz = encode_utf8(codepoint, buf);
|
||||
#endif
|
||||
if (sz) schedule_write_to_child(w->id, buf, sz);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_modifier_key(int key) {
|
||||
switch(key) {
|
||||
@@ -94,7 +78,7 @@ send_key_to_child(Window *w, int key, int mods, int action) {
|
||||
|
||||
static inline bool
|
||||
is_ascii_control_char(char c) {
|
||||
return (0 <= c && c <= 31) || c == 127;
|
||||
return c == 0 || (1 <= c && c <= 31) || c == 127;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -102,14 +86,14 @@ on_key_input(int key, int scancode, int action, int mods, const char* text, int
|
||||
Window *w = active_window();
|
||||
if (!w) return;
|
||||
if (global_state.in_sequence_mode) {
|
||||
if (action != GLFW_RELEASE) call_boss(process_sequence, "iiii", key, scancode, action, mods);
|
||||
if (
|
||||
action != GLFW_RELEASE &&
|
||||
key != GLFW_KEY_LEFT_SHIFT && key != GLFW_KEY_RIGHT_SHIFT && key != GLFW_KEY_LEFT_ALT && key != GLFW_KEY_RIGHT_ALT && key != GLFW_KEY_LEFT_CONTROL && key != GLFW_KEY_RIGHT_CONTROL
|
||||
) call_boss(process_sequence, "iiii", key, scancode, action, mods);
|
||||
return;
|
||||
}
|
||||
Screen *screen = w->render_data.screen;
|
||||
bool has_text = text && !is_ascii_control_char(text[0]);
|
||||
#ifdef __APPLE__
|
||||
if (has_text && IS_ALT_MODS(mods) && OPT(macos_option_as_alt)) has_text = false;
|
||||
#endif
|
||||
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
|
||||
uint16_t qkey = (0 <= key && key < (ssize_t)arraysz(key_map)) ? key_map[key] : UINT8_MAX;
|
||||
bool special = false;
|
||||
|
||||
1736
kitty/keys.h
generated
1736
kitty/keys.h
generated
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,9 @@ def modify_complex_key(name, amt):
|
||||
return modify_key_bytes(key_as_bytes(name), amt)
|
||||
|
||||
|
||||
control_codes = {}
|
||||
control_codes = {
|
||||
defines.GLFW_KEY_BACKSPACE: b'\x08'
|
||||
}
|
||||
smkx_key_map = {}
|
||||
alt_codes = {
|
||||
defines.GLFW_KEY_TAB: b'\033\t',
|
||||
@@ -37,6 +39,17 @@ shift_alt_codes[defines.GLFW_KEY_TAB] = key_as_bytes('kcbt')
|
||||
alt_mods = (defines.GLFW_MOD_ALT, defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT)
|
||||
ctrl_shift_mod = defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL
|
||||
ctrl_alt_mod = defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL
|
||||
SHIFTED_KEYS = {
|
||||
defines.GLFW_KEY_TAB: key_as_bytes('kcbt'),
|
||||
defines.GLFW_KEY_HOME: key_as_bytes('kHOM'),
|
||||
defines.GLFW_KEY_END: key_as_bytes('kEND'),
|
||||
defines.GLFW_KEY_LEFT: key_as_bytes('kLFT'),
|
||||
defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'),
|
||||
defines.GLFW_KEY_UP: key_as_bytes('kri'),
|
||||
defines.GLFW_KEY_DOWN: key_as_bytes('kind'),
|
||||
}
|
||||
|
||||
control_alt_codes = {}
|
||||
|
||||
for kf, kn in {
|
||||
defines.GLFW_KEY_UP: 'kcuu1',
|
||||
@@ -54,13 +67,20 @@ for kf, kn in {
|
||||
alt_codes[kf] = modify_complex_key(kn, 3)
|
||||
shift_alt_codes[kf] = modify_complex_key(kn, 4)
|
||||
control_codes[kf] = modify_complex_key(kn, 5)
|
||||
control_alt_codes[kf] = modify_complex_key(kn, 7)
|
||||
for f in range(1, 13):
|
||||
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
|
||||
kn = 'kf{}'.format(f)
|
||||
smkx_key_map[kf] = key_as_bytes(kn)
|
||||
SHIFTED_KEYS[kf] = modify_complex_key(kn, 2)
|
||||
alt_codes[kf] = modify_complex_key(kn, 3)
|
||||
shift_alt_codes[kf] = modify_complex_key(kn, 4)
|
||||
control_codes[kf] = modify_complex_key(kn, 5)
|
||||
control_alt_codes[kf] = modify_complex_key(kn, 7)
|
||||
for f in range(13, 26):
|
||||
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
|
||||
kn = 'kf{}'.format(f)
|
||||
smkx_key_map[kf] = key_as_bytes(kn)
|
||||
f = {k: k for k in '0123456789'}
|
||||
f.update({
|
||||
'COMMA': ',',
|
||||
@@ -80,16 +100,6 @@ smkx_key_map[defines.GLFW_KEY_KP_ENTER] = b'\r'
|
||||
smkx_key_map[defines.GLFW_KEY_BACKSPACE] = key_as_bytes('kbs')
|
||||
smkx_key_map[defines.GLFW_KEY_TAB] = b'\t'
|
||||
|
||||
SHIFTED_KEYS = {
|
||||
defines.GLFW_KEY_TAB: key_as_bytes('kcbt'),
|
||||
defines.GLFW_KEY_HOME: key_as_bytes('kHOM'),
|
||||
defines.GLFW_KEY_END: key_as_bytes('kEND'),
|
||||
defines.GLFW_KEY_LEFT: key_as_bytes('kLFT'),
|
||||
defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'),
|
||||
defines.GLFW_KEY_UP: key_as_bytes('kri'),
|
||||
defines.GLFW_KEY_DOWN: key_as_bytes('kind'),
|
||||
}
|
||||
|
||||
control_codes.update({
|
||||
k: (1 + i, )
|
||||
for i, k in
|
||||
@@ -206,6 +216,7 @@ ASCII_C0_SHIFTED = {
|
||||
}
|
||||
CTRL_SHIFT_KEYS = {getattr(defines, 'GLFW_KEY_' + k): v for k, v in ASCII_C0_SHIFTED.items()}
|
||||
CTRL_ALT_KEYS = {getattr(defines, 'GLFW_KEY_' + k) for k in string.ascii_uppercase}
|
||||
all_control_alt_keys = set(CTRL_ALT_KEYS) | set(control_alt_codes)
|
||||
|
||||
|
||||
def key_to_bytes(key, smkx, extended, mods, action):
|
||||
@@ -224,8 +235,11 @@ def key_to_bytes(key, smkx, extended, mods, action):
|
||||
m = UN_SHIFTED_PRINTABLE if mods == defines.GLFW_MOD_ALT else SHIFTED_PRINTABLE
|
||||
data.append(0o33)
|
||||
data.extend(m[key])
|
||||
elif mods == ctrl_alt_mod and key in CTRL_ALT_KEYS:
|
||||
data.append(0x1b), data.extend(control_codes[key])
|
||||
elif mods == ctrl_alt_mod and key in all_control_alt_keys:
|
||||
if key in CTRL_ALT_KEYS:
|
||||
data.append(0x1b), data.extend(control_codes[key])
|
||||
else:
|
||||
data.extend(control_alt_codes[key])
|
||||
else:
|
||||
key_map = cursor_key_mode_map[smkx]
|
||||
x = key_map.get(key)
|
||||
@@ -256,7 +270,7 @@ def shortcut_matches(s, mods, key, scancode):
|
||||
|
||||
|
||||
def generate_key_table():
|
||||
# To run this, use: python3 . -c "from kitty.keys import *; generate_key_table()"
|
||||
# To run this, use: python3 . +runpy "from kitty.keys import *; generate_key_table()"
|
||||
import os
|
||||
from functools import partial
|
||||
f = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'keys.h'), 'w')
|
||||
|
||||
43
kitty/khrplatform.h
generated
43
kitty/khrplatform.h
generated
@@ -1,8 +1,7 @@
|
||||
#ifndef __khrplatform_h_
|
||||
#define __khrplatform_h_
|
||||
|
||||
/*
|
||||
** Copyright (c) 2008-2009 The Khronos Group Inc.
|
||||
** Copyright (c) 2008-2018 The Khronos Group Inc.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a
|
||||
** copy of this software and/or associated documentation files (the
|
||||
@@ -23,21 +22,18 @@
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
||||
*/
|
||||
|
||||
/* Khronos platform-specific types and definitions.
|
||||
*
|
||||
* $Revision: 32517 $ on $Date: 2016-03-11 02:41:19 -0800 (Fri, 11 Mar 2016) $
|
||||
* The master copy of khrplatform.h is maintained in the Khronos EGL
|
||||
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
|
||||
* The last semantic modification to khrplatform.h was at commit ID:
|
||||
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
|
||||
*
|
||||
* Adopters may modify this file to suit their platform. Adopters are
|
||||
* encouraged to submit platform specific modifications to the Khronos
|
||||
* group so that they can be included in future versions of this file.
|
||||
* Please submit changes by sending them to the public Khronos Bugzilla
|
||||
* (http://khronos.org/bugzilla) by filing a bug against product
|
||||
* "Khronos (general)" component "Registry".
|
||||
*
|
||||
* A predefined template which fills in some of the bug fields can be
|
||||
* reached using http://tinyurl.com/khrplatform-h-bugreport, but you
|
||||
* must create a Bugzilla login first.
|
||||
* Please submit changes by filing pull requests or issues on
|
||||
* the EGL Registry repository linked above.
|
||||
*
|
||||
*
|
||||
* See the Implementer's Guidelines for information about where this file
|
||||
@@ -91,7 +87,6 @@
|
||||
* int arg1,
|
||||
* int arg2) KHRONOS_APIATTRIBUTES;
|
||||
*/
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APICALL
|
||||
*-------------------------------------------------------------------------
|
||||
@@ -106,7 +101,6 @@
|
||||
#else
|
||||
# define KHRONOS_APICALL
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIENTRY
|
||||
*-------------------------------------------------------------------------
|
||||
@@ -119,7 +113,6 @@
|
||||
#else
|
||||
# define KHRONOS_APIENTRY
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIATTRIBUTES
|
||||
*-------------------------------------------------------------------------
|
||||
@@ -130,13 +123,10 @@
|
||||
#else
|
||||
#define KHRONOS_APIATTRIBUTES
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* basic type definitions
|
||||
*-----------------------------------------------------------------------*/
|
||||
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
|
||||
|
||||
|
||||
/*
|
||||
* Using <stdint.h>
|
||||
*/
|
||||
@@ -147,9 +137,7 @@ typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(__VMS ) || defined(__sgi)
|
||||
|
||||
/*
|
||||
* Using <inttypes.h>
|
||||
*/
|
||||
@@ -160,9 +148,7 @@ typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
|
||||
|
||||
/*
|
||||
* Win32
|
||||
*/
|
||||
@@ -172,9 +158,7 @@ typedef __int64 khronos_int64_t;
|
||||
typedef unsigned __int64 khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(__sun__) || defined(__digital__)
|
||||
|
||||
/*
|
||||
* Sun or Digital
|
||||
*/
|
||||
@@ -189,9 +173,7 @@ typedef unsigned long long int khronos_uint64_t;
|
||||
#endif /* __arch64__ */
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif 0
|
||||
|
||||
/*
|
||||
* Hypothetical platform with no float or int64 support
|
||||
*/
|
||||
@@ -199,9 +181,7 @@ typedef int khronos_int32_t;
|
||||
typedef unsigned int khronos_uint32_t;
|
||||
#define KHRONOS_SUPPORT_INT64 0
|
||||
#define KHRONOS_SUPPORT_FLOAT 0
|
||||
|
||||
#else
|
||||
|
||||
/*
|
||||
* Generic fallback
|
||||
*/
|
||||
@@ -212,10 +192,7 @@ typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Types that are (so far) the same on all platforms
|
||||
*/
|
||||
@@ -223,7 +200,6 @@ typedef signed char khronos_int8_t;
|
||||
typedef unsigned char khronos_uint8_t;
|
||||
typedef signed short int khronos_int16_t;
|
||||
typedef unsigned short int khronos_uint16_t;
|
||||
|
||||
/*
|
||||
* Types that differ between LLP64 and LP64 architectures - in LLP64,
|
||||
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
|
||||
@@ -240,14 +216,12 @@ typedef unsigned long int khronos_uintptr_t;
|
||||
typedef signed long int khronos_ssize_t;
|
||||
typedef unsigned long int khronos_usize_t;
|
||||
#endif
|
||||
|
||||
#if KHRONOS_SUPPORT_FLOAT
|
||||
/*
|
||||
* Float type
|
||||
*/
|
||||
typedef float khronos_float_t;
|
||||
#endif
|
||||
|
||||
#if KHRONOS_SUPPORT_INT64
|
||||
/* Time types
|
||||
*
|
||||
@@ -261,14 +235,12 @@ typedef float khronos_float_t;
|
||||
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
|
||||
typedef khronos_int64_t khronos_stime_nanoseconds_t;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Dummy value used to pad enum types to 32 bits.
|
||||
*/
|
||||
#ifndef KHRONOS_MAX_ENUM
|
||||
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Enumerated boolean type
|
||||
*
|
||||
@@ -280,5 +252,4 @@ typedef enum {
|
||||
KHRONOS_TRUE = 1,
|
||||
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
|
||||
} khronos_boolean_enum_t;
|
||||
|
||||
#endif /* __khrplatform_h_ */
|
||||
|
||||
175
kitty/kitty.conf
175
kitty/kitty.conf
@@ -2,7 +2,9 @@
|
||||
|
||||
# You can include secondary config files via the "include" directive.
|
||||
# If you use a relative path for include, it is resolved with respect to the
|
||||
# location od the current config file. For example:
|
||||
# location of the current config file. Note that environment variables
|
||||
# are expanded, so ${USER}.conf becomes name.conf if USER=name
|
||||
# For example:
|
||||
# include other.conf
|
||||
|
||||
# Fonts {{{
|
||||
@@ -101,7 +103,7 @@ url_color #0087BD
|
||||
url_style curly
|
||||
|
||||
# The modifier keys to press when clicking with the mouse on URLs to open the URL
|
||||
open_url_modifiers ctrl+shift
|
||||
open_url_modifiers kitty_mod
|
||||
|
||||
# The program with which to open URLs that are clicked on. The special value "default" means to
|
||||
# use the operating system's default URL handler.
|
||||
@@ -166,6 +168,14 @@ visual_bell_duration 0.0
|
||||
|
||||
# Enable/disable the audio bell. Useful in environments that require silence.
|
||||
enable_audio_bell yes
|
||||
|
||||
# Request window attention on bell.
|
||||
# Makes the dock icon bounce on macOS or the taskbar flash on linux.
|
||||
window_alert_on_bell yes
|
||||
|
||||
# Show a bell symbol on the tab if a bell occurs in one of the windows in the
|
||||
# tab and the window is not the currently focused window
|
||||
bell_on_tab yes
|
||||
# }}}
|
||||
|
||||
|
||||
@@ -182,6 +192,12 @@ initial_window_height 400
|
||||
# For a list of available layouts, see the README.
|
||||
enabled_layouts *
|
||||
|
||||
# The step size (in units of cell width/cell height) to use when resizing
|
||||
# windows. The cells value is used for horizontal resizing and the lines value
|
||||
# for vertical resizing.
|
||||
window_resize_step_cells 2
|
||||
window_resize_step_lines 2
|
||||
|
||||
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
||||
# Note that borders are displayed only when more than one window is visible. They are meant to separate multiple windows.
|
||||
window_border_width 1
|
||||
@@ -198,6 +214,9 @@ active_border_color #00ff00
|
||||
# The color for the border of inactive windows
|
||||
inactive_border_color #cccccc
|
||||
|
||||
# The color for the border of inactive windows in which a bell has occurred
|
||||
bell_border_color #ff5a00
|
||||
|
||||
# Fade the text in inactive windows by the specified amount (a number between
|
||||
# zero and one, with 0 being fully faded).
|
||||
inactive_text_alpha 1.0
|
||||
@@ -240,6 +259,10 @@ background #000000
|
||||
# Be aware that using a value less than 1.0 is a (possibly significant) performance hit.
|
||||
background_opacity 1.0
|
||||
|
||||
# How much to dim text that has the DIM/FAINT attribute set. 1.0 means no dimming and
|
||||
# 0.0 means fully dimmed (i.e. invisible).
|
||||
dim_opacity 0.75
|
||||
|
||||
# The foreground for selections
|
||||
selection_foreground #000000
|
||||
|
||||
@@ -307,6 +330,14 @@ close_on_child_death no
|
||||
# Note that this even works over ssh connections.
|
||||
allow_remote_control no
|
||||
|
||||
# Allow programs running in kitty to read and write from the clipboard. You can
|
||||
# control exactly which actions are allowed. The set of possible actions is:
|
||||
# write-clipboard read-clipboard write-primary read-primary
|
||||
# The default is to allow writing to the clipboard and primary selection. Note
|
||||
# that enabling the read functionality is a security risk as it means that any
|
||||
# program, even one running on a remote server via SSH can read your clipboard.
|
||||
clipboard_control write-clipboard write-primary
|
||||
|
||||
# The value of the TERM environment variable to set. Changing this can break
|
||||
# many terminal programs, only change it if you know what you are doing, not
|
||||
# because you read some advice on Stack Overflow to change it.
|
||||
@@ -319,6 +350,11 @@ term xterm-kitty
|
||||
# Keyboard shortcuts {{{
|
||||
# For a list of key names, see: http://www.glfw.org/docs/latest/group__keys.html
|
||||
# For a list of modifier names, see: http://www.glfw.org/docs/latest/group__mods.html
|
||||
|
||||
# The value of kitty_mod is used as the modifier for all default shortcuts, you
|
||||
# can change it in your kitty.conf to change the modifiers for all the default
|
||||
# shortcuts.
|
||||
kitty_mod ctrl+shift
|
||||
#
|
||||
# You can use the special action no_op to unmap a keyboard shortcut that is
|
||||
# assigned in the default configuration.
|
||||
@@ -327,7 +363,7 @@ term xterm-kitty
|
||||
# syntax below:
|
||||
# map key combine <separator> action1 <separator> action2 <separator> action3 ...
|
||||
# For example:
|
||||
# map ctrl+shift+e combine : new_window : next_layout
|
||||
# map kitty_mod+e combine : new_window : next_layout
|
||||
# this will create a new window and switch to the next available layout
|
||||
#
|
||||
# You can use multi-key shortcuts using the syntax shown below:
|
||||
@@ -337,59 +373,61 @@ term xterm-kitty
|
||||
# this will change the font size to 20 points when you press ctrl+f and then 2
|
||||
|
||||
# Clipboard {{{
|
||||
map ctrl+shift+v paste_from_clipboard
|
||||
map ctrl+shift+s paste_from_selection
|
||||
map ctrl+shift+c copy_to_clipboard
|
||||
map shift+insert paste_from_selection
|
||||
map kitty_mod+v paste_from_clipboard
|
||||
map kitty_mod+s paste_from_selection
|
||||
map kitty_mod+c copy_to_clipboard
|
||||
map shift+insert paste_from_selection
|
||||
# You can also pass the contents of the current selection to any program using
|
||||
# pass_selection_to_program. By default, the system's open program is used, but
|
||||
# you can specify your own, for example:
|
||||
# map ctrl+shift+o pass_selection_to_program firefox
|
||||
map ctrl+shift+o pass_selection_to_program
|
||||
# map kitty_mod+o pass_selection_to_program firefox
|
||||
map kitty_mod+o pass_selection_to_program
|
||||
# }}}
|
||||
|
||||
# Scrolling {{{
|
||||
map ctrl+shift+up scroll_line_up
|
||||
map ctrl+shift+down scroll_line_down
|
||||
map ctrl+shift+k scroll_line_up
|
||||
map ctrl+shift+j scroll_line_down
|
||||
map ctrl+shift+page_up scroll_page_up
|
||||
map ctrl+shift+page_down scroll_page_down
|
||||
map ctrl+shift+home scroll_home
|
||||
map ctrl+shift+end scroll_end
|
||||
map ctrl+shift+h show_scrollback
|
||||
map kitty_mod+up scroll_line_up
|
||||
map kitty_mod+down scroll_line_down
|
||||
map kitty_mod+k scroll_line_up
|
||||
map kitty_mod+j scroll_line_down
|
||||
map kitty_mod+page_up scroll_page_up
|
||||
map kitty_mod+page_down scroll_page_down
|
||||
map kitty_mod+home scroll_home
|
||||
map kitty_mod+end scroll_end
|
||||
map kitty_mod+h show_scrollback
|
||||
# }}}
|
||||
|
||||
# Window management {{{
|
||||
map ctrl+shift+enter new_window
|
||||
map ctrl+shift+n new_os_window
|
||||
map ctrl+shift+w close_window
|
||||
map ctrl+shift+] next_window
|
||||
map ctrl+shift+[ previous_window
|
||||
map ctrl+shift+f move_window_forward
|
||||
map ctrl+shift+b move_window_backward
|
||||
map ctrl+shift+` move_window_to_top
|
||||
map ctrl+shift+1 first_window
|
||||
map ctrl+shift+2 second_window
|
||||
map ctrl+shift+3 third_window
|
||||
map ctrl+shift+4 fourth_window
|
||||
map ctrl+shift+5 fifth_window
|
||||
map ctrl+shift+6 sixth_window
|
||||
map ctrl+shift+7 seventh_window
|
||||
map ctrl+shift+8 eighth_window
|
||||
map ctrl+shift+9 ninth_window
|
||||
map ctrl+shift+0 tenth_window
|
||||
map kitty_mod+enter new_window
|
||||
map kitty_mod+n new_os_window
|
||||
map kitty_mod+w close_window
|
||||
map kitty_mod+] next_window
|
||||
map kitty_mod+[ previous_window
|
||||
map kitty_mod+f move_window_forward
|
||||
map kitty_mod+b move_window_backward
|
||||
map kitty_mod+` move_window_to_top
|
||||
map kitty_mod+r start_resizing_window
|
||||
# Switching to a particular window
|
||||
map kitty_mod+1 first_window
|
||||
map kitty_mod+2 second_window
|
||||
map kitty_mod+3 third_window
|
||||
map kitty_mod+4 fourth_window
|
||||
map kitty_mod+5 fifth_window
|
||||
map kitty_mod+6 sixth_window
|
||||
map kitty_mod+7 seventh_window
|
||||
map kitty_mod+8 eighth_window
|
||||
map kitty_mod+9 ninth_window
|
||||
map kitty_mod+0 tenth_window
|
||||
# You can open a new window running an arbitrary program, for example:
|
||||
# map ctrl+shift+y new_window mutt
|
||||
# map kitty_mod+y new_window mutt
|
||||
#
|
||||
# You can pass the current selection to the new program by using the @selection placeholder
|
||||
# map ctrl+shift+y new_window less @selection
|
||||
# map kitty_mod+y new_window less @selection
|
||||
#
|
||||
# You can even send the contents of the current screen + history buffer as stdin using
|
||||
# the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes).
|
||||
# For only the current screen, use @screen or @ansi_screen.
|
||||
# For example, the following command opens the scrollback buffer in less in a new window.
|
||||
# map ctrl+shift+y new_window @ansi less +G -R
|
||||
# map kitty_mod+y new_window @ansi less +G -R
|
||||
#
|
||||
# You can open a new window with the current working directory set to the
|
||||
# working directory of the current window using
|
||||
@@ -397,14 +435,14 @@ map ctrl+shift+0 tenth_window
|
||||
# }}}
|
||||
|
||||
# Tab management {{{
|
||||
map ctrl+shift+right next_tab
|
||||
map ctrl+shift+left previous_tab
|
||||
map ctrl+shift+t new_tab
|
||||
map ctrl+shift+q close_tab
|
||||
map ctrl+shift+l next_layout
|
||||
map ctrl+shift+. move_tab_forward
|
||||
map ctrl+shift+, move_tab_backward
|
||||
map ctrl+shift+alt+t set_tab_title
|
||||
map kitty_mod+right next_tab
|
||||
map kitty_mod+left previous_tab
|
||||
map kitty_mod+t new_tab
|
||||
map kitty_mod+q close_tab
|
||||
map kitty_mod+l next_layout
|
||||
map kitty_mod+. move_tab_forward
|
||||
map kitty_mod+, move_tab_backward
|
||||
map kitty_mod+alt+t set_tab_title
|
||||
# You can also create shortcuts to go to specific tabs, with 1 being the first tab
|
||||
# map ctrl+alt+1 goto_tab 1
|
||||
# map ctrl+alt+2 goto_tab 2
|
||||
@@ -420,12 +458,12 @@ map ctrl+shift+alt+t set_tab_title
|
||||
# }}}
|
||||
|
||||
# Font sizes {{{
|
||||
map ctrl+shift+equal increase_font_size
|
||||
map ctrl+shift+minus decrease_font_size
|
||||
map ctrl+shift+backspace restore_font_size
|
||||
map kitty_mod+equal increase_font_size
|
||||
map kitty_mod+minus decrease_font_size
|
||||
map kitty_mod+backspace restore_font_size
|
||||
# To setup shortcuts for specific font sizes, follow the example below:
|
||||
# map ctrl+shift+f6 set_font_size 10.0
|
||||
# map ctrl+shift+f7 set_font_size 20.5
|
||||
# map kitty_mod+f6 set_font_size 10.0
|
||||
# map kitty_mod+f7 set_font_size 20.5
|
||||
# }}}
|
||||
|
||||
# Select and act on visible text {{{
|
||||
@@ -434,33 +472,32 @@ map ctrl+shift+backspace restore_font_size
|
||||
#
|
||||
# Open a currently visible URL using the keyboard. The program used to open the
|
||||
# URL is specified in open_url_with.
|
||||
map ctrl+shift+e run_kitten text hints
|
||||
map kitty_mod+e kitten hints
|
||||
|
||||
# Select a path/filename and insert it into the terminal. Useful, for instance to
|
||||
# run git commands on a filename output from a previous git command.
|
||||
map ctrl+shift+p>f run_kitten text hints --type path --program -
|
||||
map kitty_mod+p>f kitten hints --type path --program -
|
||||
|
||||
# Select a path/filename and open it with the default open program.
|
||||
map ctrl+shift+p>shift+f run_kitten text hints --type path
|
||||
map kitty_mod+p>shift+f kitten hints --type path
|
||||
|
||||
# Select a line of text and insert it into the terminal. Use for the
|
||||
# output of things like: ls -1
|
||||
map ctrl+shift+p>l run_kitten text hints --type line --program -
|
||||
map kitty_mod+p>l kitten hints --type line --program -
|
||||
|
||||
# Select words and insert into terminal.
|
||||
map ctrl+shift+p>w run_kitten text hints --type word --program -
|
||||
map kitty_mod+p>w kitten hints --type word --program -
|
||||
|
||||
# The hints kitten has many more modes of operation that you can map to different
|
||||
# shortcuts. For a full description run: kitty +kitten hints --help
|
||||
# }}}
|
||||
|
||||
# Miscellaneous {{{
|
||||
map ctrl+shift+f11 toggle_fullscreen
|
||||
map ctrl+shift+u input_unicode_character
|
||||
map ctrl+shift+f2 edit_config_file
|
||||
You can customize how the URLs are
|
||||
map kitty_mod+f11 toggle_fullscreen
|
||||
map kitty_mod+u input_unicode_character
|
||||
map kitty_mod+f2 edit_config_file
|
||||
# Open the kitty shell in a new window/tab/overlay/os_window to control kitty using commands.
|
||||
map ctrl+shift+escape kitty_shell window
|
||||
map kitty_mod+escape kitty_shell window
|
||||
|
||||
# Sending arbitrary text on shortcut key presses
|
||||
# You can tell kitty to send arbitrary (UTF-8) encoded text to
|
||||
@@ -480,6 +517,9 @@ map ctrl+shift+escape kitty_shell window
|
||||
# map ctrl+alt+a send_text application Word\x1bOH
|
||||
# }}}
|
||||
|
||||
# You can have kitty remove all shortcut definition seen up to this point. Useful, for
|
||||
# instance, to remove the default shortcuts.
|
||||
clear_all_shortcuts no
|
||||
# }}}
|
||||
|
||||
|
||||
@@ -487,8 +527,12 @@ map ctrl+shift+escape kitty_shell window
|
||||
|
||||
# Change the color of the kitty window's titlebar on macOS. A value of "system"
|
||||
# means to use the default system color, a value of "background" means to use
|
||||
# the default background color and finally you can use an arbitrary color, such
|
||||
# as #12af59 or "red".
|
||||
# the background color of the currently active window and finally you can use
|
||||
# an arbitrary color, such as #12af59 or "red". WARNING: This option works by
|
||||
# using a hack, as there is no proper Cocoa API for it. It sets the background
|
||||
# color of the entire window and makes the titlebar transparent. As such it is
|
||||
# incompatible with background_opacity. If you want to use both, you are
|
||||
# probably better off just hiding the titlebar with macos_hide_titlebar.
|
||||
macos_titlebar_color system
|
||||
|
||||
# Hide the kitty window's title bar on macOS.
|
||||
@@ -499,4 +543,7 @@ macos_hide_titlebar no
|
||||
# break any Alt+key keyboard shortcuts in your terminal programs, but you
|
||||
# can use the macOS unicode input technique.
|
||||
macos_option_as_alt yes
|
||||
|
||||
# Hide the kitty window from running tasks (alt-tab) on macOS.
|
||||
macos_hide_from_tasks no
|
||||
# }}}
|
||||
|
||||
490
kitty/layout.py
490
kitty/layout.py
@@ -4,13 +4,14 @@
|
||||
|
||||
from collections import namedtuple
|
||||
from functools import partial
|
||||
from itertools import islice
|
||||
from itertools import islice, repeat
|
||||
|
||||
from .constants import WindowGeometry
|
||||
from .fast_data_types import (
|
||||
Region, pt_to_px, set_active_window, swap_windows, viewport_for_window
|
||||
Region, set_active_window, swap_windows, viewport_for_window
|
||||
)
|
||||
|
||||
# Utils {{{
|
||||
central = Region((0, 0, 199, 199, 200, 200))
|
||||
cell_width = cell_height = 20
|
||||
|
||||
@@ -21,7 +22,7 @@ def idx_for_id(win_id, windows):
|
||||
return i
|
||||
|
||||
|
||||
def layout_dimension(start_at, length, cell_length, number_of_windows=1, border_length=0, margin_length=0, padding_length=0, left_align=False):
|
||||
def layout_dimension(start_at, length, cell_length, number_of_windows=1, border_length=0, margin_length=0, padding_length=0, left_align=False, bias=None):
|
||||
number_of_cells = length // cell_length
|
||||
border_length += padding_length
|
||||
space_needed_for_border = number_of_windows * 2 * border_length
|
||||
@@ -37,8 +38,30 @@ def layout_dimension(start_at, length, cell_length, number_of_windows=1, border_
|
||||
if not left_align:
|
||||
pos += extra // 2
|
||||
pos += border_length + margin_length
|
||||
inner_length = cells_per_window * cell_length
|
||||
window_length = 2 * (border_length + margin_length) + inner_length
|
||||
|
||||
def calc_window_length(cells_in_window):
|
||||
inner_length = cells_in_window * cell_length
|
||||
return 2 * (border_length + margin_length) + inner_length
|
||||
|
||||
if bias is not None and number_of_windows > 1 and len(bias) == number_of_windows and cells_per_window > 5:
|
||||
cells_map = [int(b * number_of_cells) for b in bias]
|
||||
while min(cells_map) < 5:
|
||||
maxi, mini = map(cells_map.index, (max(cells_map), min(cells_map)))
|
||||
if maxi == mini:
|
||||
break
|
||||
cells_map[mini] += 1
|
||||
cells_map[maxi] -= 1
|
||||
|
||||
extra = number_of_cells - sum(cells_map)
|
||||
if extra:
|
||||
cells_map[-1] += extra
|
||||
for cells_per_window in cells_map:
|
||||
window_length = calc_window_length(cells_per_window)
|
||||
yield pos, cells_per_window
|
||||
pos += window_length
|
||||
return
|
||||
|
||||
window_length = calc_window_length(cells_per_window)
|
||||
extra = number_of_cells - (cells_per_window * number_of_windows)
|
||||
while number_of_windows > 0:
|
||||
number_of_windows -= 1
|
||||
@@ -56,28 +79,140 @@ def process_overlaid_windows(all_windows):
|
||||
return overlaid_windows, windows
|
||||
|
||||
|
||||
class Layout:
|
||||
def window_geometry(xstart, xnum, ystart, ynum):
|
||||
return WindowGeometry(left=xstart, top=ystart, xnum=xnum, ynum=ynum, right=xstart + cell_width * xnum, bottom=ystart + cell_height * ynum)
|
||||
|
||||
|
||||
def layout_single_window(margin_length, padding_length):
|
||||
xstart, xnum = next(layout_dimension(central.left, central.width, cell_width, margin_length=margin_length, padding_length=padding_length))
|
||||
ystart, ynum = next(layout_dimension(central.top, central.height, cell_height, margin_length=margin_length, padding_length=padding_length))
|
||||
return window_geometry(xstart, xnum, ystart, ynum)
|
||||
|
||||
|
||||
def left_blank_rect(w, rects):
|
||||
lt = w.geometry.left
|
||||
if lt > central.left:
|
||||
rects.append(Rect(central.left, central.top, lt, central.bottom + 1))
|
||||
|
||||
|
||||
def right_blank_rect(w, rects):
|
||||
r = w.geometry.right
|
||||
if r < central.right:
|
||||
rects.append(Rect(r, central.top, central.right + 1, central.bottom + 1))
|
||||
|
||||
|
||||
def top_blank_rect(w, rects):
|
||||
t = w.geometry.top
|
||||
if t > central.top:
|
||||
rects.append(Rect(central.left, central.top, central.right + 1, t))
|
||||
|
||||
|
||||
def bottom_blank_rect(w, rects):
|
||||
b = w.geometry.bottom
|
||||
# Need to use <= here as otherwise a single pixel row at the bottom of the
|
||||
# window is sometimes not covered. See https://github.com/kovidgoyal/kitty/issues/506
|
||||
if b <= central.bottom:
|
||||
rects.append(Rect(central.left, b, central.right + 1, central.bottom + 1))
|
||||
|
||||
|
||||
def blank_rects_for_window(w):
|
||||
ans = []
|
||||
left_blank_rect(w, ans), top_blank_rect(w, ans), right_blank_rect(w, ans), bottom_blank_rect(w, ans)
|
||||
return ans
|
||||
|
||||
|
||||
def safe_increment_bias(old_val, increment):
|
||||
return max(0.1, min(old_val + increment, 0.9))
|
||||
|
||||
|
||||
def normalize_biases(biases):
|
||||
s = sum(biases)
|
||||
if s == 1:
|
||||
return biases
|
||||
return [x/s for x in biases]
|
||||
|
||||
|
||||
def distribute_indexed_bias(base_bias, index_bias_map):
|
||||
if not index_bias_map:
|
||||
return base_bias
|
||||
ans = list(base_bias)
|
||||
limit = len(ans)
|
||||
for row, increment in index_bias_map.items():
|
||||
if row >= limit or not increment:
|
||||
continue
|
||||
other_increment = -increment / (limit - 1)
|
||||
ans = [safe_increment_bias(b, increment if i == row else other_increment) for i, b in enumerate(ans)]
|
||||
return normalize_biases(ans)
|
||||
|
||||
|
||||
def variable_bias(num_windows, candidate):
|
||||
return distribute_indexed_bias(list(repeat(1/(num_windows), num_windows)), candidate)
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
class Layout: # {{{
|
||||
|
||||
name = None
|
||||
needs_window_borders = True
|
||||
only_active_window_visible = False
|
||||
|
||||
def __init__(self, os_window_id, tab_id, opts, border_width):
|
||||
def __init__(self, os_window_id, tab_id, margin_width, padding_width, border_width, layout_opts=''):
|
||||
self.os_window_id = os_window_id
|
||||
self.tab_id = tab_id
|
||||
self.set_active_window_in_os_window = partial(set_active_window, os_window_id, tab_id)
|
||||
self.swap_windows_in_os_window = partial(swap_windows, os_window_id, tab_id)
|
||||
self.opts = opts
|
||||
self.border_width = border_width
|
||||
self.margin_width = pt_to_px(opts.window_margin_width)
|
||||
self.padding_width = pt_to_px(opts.window_padding_width)
|
||||
self.margin_width = margin_width
|
||||
self.padding_width = padding_width
|
||||
# A set of rectangles corresponding to the blank spaces at the edges of
|
||||
# this layout, i.e. spaces that are not covered by any window
|
||||
self.blank_rects = ()
|
||||
self.blank_rects = []
|
||||
self.layout_opts = self.parse_layout_opts(layout_opts)
|
||||
self.full_name = self.name + ((':' + layout_opts) if layout_opts else '')
|
||||
self.remove_all_biases()
|
||||
|
||||
def nth_window(self, all_windows, num):
|
||||
def bias_increment_for_cell(self, is_horizontal):
|
||||
self._set_dimensions()
|
||||
if is_horizontal:
|
||||
return (cell_width + 1) / central.width
|
||||
return (cell_height + 1) / central.height
|
||||
|
||||
def apply_bias(self, idx, increment_as_percent, num_windows, is_horizontal):
|
||||
return False
|
||||
|
||||
def remove_all_biases(self):
|
||||
return False
|
||||
|
||||
def modify_size_of_window(self, all_windows, window_id, increment, is_horizontal=True):
|
||||
idx = idx_for_id(window_id, all_windows)
|
||||
if idx is None:
|
||||
return False
|
||||
w = all_windows[idx]
|
||||
windows = process_overlaid_windows(all_windows)[1]
|
||||
idx = idx_for_id(w.id, windows)
|
||||
if idx is None:
|
||||
idx = idx_for_id(w.overlay_window_id, windows)
|
||||
if idx is not None:
|
||||
return self.apply_bias(idx, increment, len(windows), is_horizontal)
|
||||
return False
|
||||
|
||||
def parse_layout_opts(self, layout_opts):
|
||||
if not layout_opts:
|
||||
return {}
|
||||
ans = {}
|
||||
for x in layout_opts.split(';'):
|
||||
k, v = x.partition('=')[::2]
|
||||
if k and v:
|
||||
ans[k] = v
|
||||
return ans
|
||||
|
||||
def nth_window(self, all_windows, num, make_active=True):
|
||||
windows = process_overlaid_windows(all_windows)[1]
|
||||
w = windows[min(num, len(windows) - 1)]
|
||||
if not make_active:
|
||||
return w
|
||||
active_window_idx = idx_for_id(w.id, all_windows)
|
||||
return self.set_active_window(all_windows, active_window_idx)
|
||||
|
||||
@@ -178,10 +313,12 @@ class Layout:
|
||||
self.set_active_window_in_os_window(active_window_idx)
|
||||
return active_window_idx
|
||||
|
||||
def __call__(self, all_windows, active_window_idx):
|
||||
def _set_dimensions(self):
|
||||
global central, cell_width, cell_height
|
||||
central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id)
|
||||
|
||||
def __call__(self, all_windows, active_window_idx):
|
||||
self._set_dimensions()
|
||||
active_window = all_windows[active_window_idx]
|
||||
overlaid_windows, windows = process_overlaid_windows(all_windows)
|
||||
if overlaid_windows:
|
||||
@@ -193,19 +330,25 @@ class Layout:
|
||||
else:
|
||||
windows = all_windows
|
||||
self.update_visibility(all_windows, active_window, overlaid_windows)
|
||||
self.blank_rects = []
|
||||
self.do_layout(windows, active_window_idx)
|
||||
return idx_for_id(active_window.id, all_windows)
|
||||
|
||||
# Utils {{{
|
||||
def xlayout(self, num):
|
||||
def layout_single_window(self, w):
|
||||
wg = layout_single_window(self.margin_width, self.padding_width)
|
||||
w.set_geometry(0, wg)
|
||||
self.blank_rects = blank_rects_for_window(w)
|
||||
|
||||
def xlayout(self, num, bias=None):
|
||||
return layout_dimension(
|
||||
central.left, central.width, cell_width, num, self.border_width,
|
||||
margin_length=self.margin_width, padding_length=self.padding_width)
|
||||
margin_length=self.margin_width, padding_length=self.padding_width, bias=bias)
|
||||
|
||||
def ylayout(self, num, left_align=True):
|
||||
def ylayout(self, num, left_align=True, bias=None):
|
||||
return layout_dimension(
|
||||
central.top, central.height, cell_height, num, self.border_width, left_align=left_align,
|
||||
margin_length=self.margin_width, padding_length=self.padding_width)
|
||||
margin_length=self.margin_width, padding_length=self.padding_width, bias=bias)
|
||||
|
||||
def simple_blank_rects(self, first_window, last_window):
|
||||
br = self.blank_rects
|
||||
@@ -220,80 +363,81 @@ class Layout:
|
||||
|
||||
def do_layout(self, windows, active_window_idx):
|
||||
raise NotImplementedError()
|
||||
# }}}
|
||||
|
||||
|
||||
def window_geometry(xstart, xnum, ystart, ynum):
|
||||
return WindowGeometry(left=xstart, top=ystart, xnum=xnum, ynum=ynum, right=xstart + cell_width * xnum, bottom=ystart + cell_height * ynum)
|
||||
|
||||
|
||||
def layout_single_window(margin_length, padding_length):
|
||||
xstart, xnum = next(layout_dimension(central.left, central.width, cell_width, margin_length=margin_length, padding_length=padding_length))
|
||||
ystart, ynum = next(layout_dimension(central.top, central.height, cell_height, margin_length=margin_length, padding_length=padding_length))
|
||||
return window_geometry(xstart, xnum, ystart, ynum)
|
||||
|
||||
|
||||
def left_blank_rect(w, rects):
|
||||
lt = w.geometry.left
|
||||
if lt > central.left:
|
||||
rects.append(Rect(central.left, central.top, lt, central.bottom + 1))
|
||||
|
||||
|
||||
def right_blank_rect(w, rects):
|
||||
r = w.geometry.right
|
||||
if r < central.right:
|
||||
rects.append(Rect(r, central.top, central.right + 1, central.bottom + 1))
|
||||
|
||||
|
||||
def top_blank_rect(w, rects):
|
||||
t = w.geometry.top
|
||||
if t > central.top:
|
||||
rects.append(Rect(central.left, central.top, central.right + 1, t))
|
||||
|
||||
|
||||
def bottom_blank_rect(w, rects):
|
||||
b = w.geometry.bottom
|
||||
if b < central.bottom:
|
||||
rects.append(Rect(central.left, b, central.right + 1, central.bottom + 1))
|
||||
|
||||
|
||||
def blank_rects_for_window(w):
|
||||
ans = []
|
||||
left_blank_rect(w, ans), top_blank_rect(w, ans), right_blank_rect(w, ans), bottom_blank_rect(w, ans)
|
||||
return ans
|
||||
|
||||
|
||||
class Stack(Layout):
|
||||
class Stack(Layout): # {{{
|
||||
|
||||
name = 'stack'
|
||||
needs_window_borders = False
|
||||
only_active_window_visible = True
|
||||
|
||||
def do_layout(self, windows, active_window_idx):
|
||||
self.blank_rects = []
|
||||
wg = layout_single_window(self.margin_width, self.padding_width)
|
||||
for i, w in enumerate(windows):
|
||||
w.set_geometry(i, wg)
|
||||
if w.is_visible_in_layout:
|
||||
self.blank_rects = blank_rects_for_window(w)
|
||||
# }}}
|
||||
|
||||
|
||||
class Tall(Layout):
|
||||
class Tall(Layout): # {{{
|
||||
|
||||
name = 'tall'
|
||||
vlayout = Layout.ylayout
|
||||
main_is_horizontal = True
|
||||
|
||||
def remove_all_biases(self):
|
||||
self.main_bias = list(self.layout_opts['bias'])
|
||||
self.biased_map = {}
|
||||
return True
|
||||
|
||||
def variable_layout(self, num_windows, biased_map):
|
||||
num_windows -= 1
|
||||
return self.vlayout(num_windows, bias=variable_bias(num_windows, biased_map) if num_windows > 1 else None)
|
||||
|
||||
def apply_bias(self, idx, increment, num_windows, is_horizontal):
|
||||
if self.main_is_horizontal == is_horizontal:
|
||||
before = self.main_bias
|
||||
if idx == 0:
|
||||
self.main_bias = [safe_increment_bias(self.main_bias[0], increment), safe_increment_bias(self.main_bias[1], -increment)]
|
||||
else:
|
||||
self.main_bias = [safe_increment_bias(self.main_bias[0], -increment), safe_increment_bias(self.main_bias[1], increment)]
|
||||
self.main_bias = normalize_biases(self.main_bias)
|
||||
after = self.main_bias
|
||||
else:
|
||||
if idx == 0 or num_windows < 3:
|
||||
return False
|
||||
idx -= 1
|
||||
before_layout = list(self.variable_layout(num_windows, self.biased_map))
|
||||
candidate = self.biased_map.copy()
|
||||
before = candidate.get(idx, 0)
|
||||
candidate[idx] = after = before + increment
|
||||
if before_layout == list(self.variable_layout(num_windows, candidate)):
|
||||
return False
|
||||
self.biased_map = candidate
|
||||
|
||||
return before != after
|
||||
|
||||
def parse_layout_opts(self, layout_opts):
|
||||
ans = Layout.parse_layout_opts(self, layout_opts)
|
||||
try:
|
||||
ans['bias'] = int(ans.get('bias', 50)) / 100
|
||||
except Exception:
|
||||
ans['bias'] = 0.5
|
||||
ans['bias'] = max(0.1, min(ans['bias'], 0.9))
|
||||
ans['bias'] = ans['bias'], 1.0 - ans['bias']
|
||||
return ans
|
||||
|
||||
def do_layout(self, windows, active_window_idx):
|
||||
self.blank_rects = []
|
||||
if len(windows) == 1:
|
||||
wg = layout_single_window(self.margin_width, self.padding_width)
|
||||
windows[0].set_geometry(0, wg)
|
||||
self.blank_rects = blank_rects_for_window(windows[0])
|
||||
return
|
||||
xlayout = self.xlayout(2)
|
||||
return self.layout_single_window(windows[0])
|
||||
xlayout = self.xlayout(2, bias=self.main_bias)
|
||||
xstart, xnum = next(xlayout)
|
||||
ystart, ynum = next(self.ylayout(1))
|
||||
ystart, ynum = next(self.vlayout(1))
|
||||
windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum))
|
||||
xstart, xnum = next(xlayout)
|
||||
ylayout = self.ylayout(len(windows) - 1)
|
||||
ylayout = self.variable_layout(len(windows), self.biased_map)
|
||||
for i, (w, (ystart, ynum)) in enumerate(zip(islice(windows, 1, None), ylayout)):
|
||||
w.set_geometry(i + 1, window_geometry(xstart, xnum, ystart, ynum))
|
||||
# right bottom blank rect
|
||||
@@ -305,19 +449,98 @@ class Tall(Layout):
|
||||
self.between_blank_rect(windows[0], windows[1])
|
||||
# left bottom blank rect
|
||||
self.bottom_blank_rect(windows[0])
|
||||
# }}}
|
||||
|
||||
|
||||
class Grid(Tall):
|
||||
class Fat(Tall): # {{{
|
||||
|
||||
name = 'fat'
|
||||
vlayout = Layout.xlayout
|
||||
main_is_horizontal = False
|
||||
|
||||
def do_layout(self, windows, active_window_idx):
|
||||
if len(windows) == 1:
|
||||
return self.layout_single_window(windows[0])
|
||||
xstart, xnum = next(self.xlayout(1))
|
||||
ylayout = self.ylayout(2, bias=self.main_bias)
|
||||
ystart, ynum = next(ylayout)
|
||||
windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum))
|
||||
xlayout = self.variable_layout(len(windows), self.biased_map)
|
||||
ystart, ynum = next(ylayout)
|
||||
for i, (w, (xstart, xnum)) in enumerate(zip(islice(windows, 1, None), xlayout)):
|
||||
w.set_geometry(i + 1, window_geometry(xstart, xnum, ystart, ynum))
|
||||
if i > 0:
|
||||
# bottom between blank rect
|
||||
self.between_blank_rect(windows[i - 1], windows[i])
|
||||
|
||||
# left, top and right blank rects
|
||||
self.simple_blank_rects(windows[0], windows[-1])
|
||||
# top bottom blank rect
|
||||
self.bottom_blank_rect(windows[0])
|
||||
# bottom blank rect
|
||||
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
|
||||
# }}}
|
||||
|
||||
|
||||
class Grid(Layout): # {{{
|
||||
|
||||
name = 'grid'
|
||||
|
||||
def do_layout(self, windows, active_window_idx):
|
||||
n = len(windows)
|
||||
if n < 4:
|
||||
return Tall.do_layout(self, windows, active_window_idx)
|
||||
self.blank_rects = []
|
||||
def remove_all_biases(self):
|
||||
self.biased_rows = {}
|
||||
self.biased_cols = {}
|
||||
return True
|
||||
|
||||
def variable_layout(self, layout_func, num_windows, biased_map):
|
||||
return layout_func(num_windows, bias=variable_bias(num_windows, biased_map) if num_windows > 1 else None)
|
||||
|
||||
def apply_bias(self, idx, increment, num_windows, is_horizontal):
|
||||
b = self.biased_cols if is_horizontal else self.biased_rows
|
||||
ncols, nrows, special_rows, special_col = self.calc_grid_size(num_windows)
|
||||
|
||||
def position_for_window_idx(idx):
|
||||
row_num = col_num = 0
|
||||
|
||||
def on_col_done(col_windows):
|
||||
nonlocal col_num, row_num
|
||||
row_num = 0
|
||||
col_num += 1
|
||||
|
||||
for window_idx, xstart, xnum, ystart, ynum in self.layout_windows(
|
||||
num_windows, nrows, ncols, special_rows, special_col, on_col_done):
|
||||
if idx == window_idx:
|
||||
return row_num, col_num
|
||||
row_num += 1
|
||||
|
||||
row_num, col_num = position_for_window_idx(idx)
|
||||
|
||||
if is_horizontal:
|
||||
b = self.biased_cols
|
||||
if ncols < 2:
|
||||
return False
|
||||
bias_idx = col_num
|
||||
layout_func = self.xlayout
|
||||
attr = 'biased_cols'
|
||||
else:
|
||||
b = self.biased_rows
|
||||
if max(nrows, special_rows) < 2:
|
||||
return False
|
||||
bias_idx = row_num
|
||||
layout_func = self.ylayout
|
||||
attr = 'biased_rows'
|
||||
|
||||
before_layout = list(self.variable_layout(layout_func, num_windows, b))
|
||||
candidate = b.copy()
|
||||
before = candidate.get(bias_idx, 0)
|
||||
candidate[bias_idx] = before + increment
|
||||
if before_layout == list(self.variable_layout(layout_func, num_windows, candidate)):
|
||||
return False
|
||||
setattr(self, attr, candidate)
|
||||
return True
|
||||
|
||||
def calc_grid_size(self, n):
|
||||
if n <= 5:
|
||||
ncols = 2
|
||||
ncols = 1 if n == 1 else 2
|
||||
else:
|
||||
for ncols in range(3, (n // 2) + 1):
|
||||
if ncols * ncols >= n:
|
||||
@@ -325,27 +548,44 @@ class Grid(Tall):
|
||||
nrows = n // ncols
|
||||
special_rows = n - (nrows * (ncols - 1))
|
||||
special_col = 0 if special_rows < nrows else ncols - 1
|
||||
return ncols, nrows, special_rows, special_col
|
||||
|
||||
def layout_windows(self, num_windows, nrows, ncols, special_rows, special_col, on_col_done=lambda col_windows: None):
|
||||
# Distribute windows top-to-bottom, left-to-right (i.e. in columns)
|
||||
xlayout = self.xlayout(ncols)
|
||||
yvals_normal = tuple(self.ylayout(nrows))
|
||||
yvals_special = yvals_normal if special_rows == nrows else tuple(self.ylayout(special_rows))
|
||||
|
||||
winmap = list(enumerate(windows))
|
||||
xlayout = self.variable_layout(self.xlayout, ncols, self.biased_cols)
|
||||
yvals_normal = tuple(self.variable_layout(self.ylayout, nrows, self.biased_rows))
|
||||
yvals_special = yvals_normal if special_rows == nrows else tuple(self.variable_layout(self.ylayout, special_rows, self.biased_rows))
|
||||
pos = 0
|
||||
win_col_map = []
|
||||
for col in range(ncols):
|
||||
rows = special_rows if col == special_col else nrows
|
||||
yl = yvals_special if col == special_col else yvals_normal
|
||||
xstart, xnum = next(xlayout)
|
||||
col_windows = []
|
||||
for (ystart, ynum), (window_idx, w) in zip(yl, winmap[pos:pos + rows]):
|
||||
w.set_geometry(window_idx, window_geometry(xstart, xnum, ystart, ynum))
|
||||
col_windows.append(w)
|
||||
# bottom blank rect
|
||||
self.bottom_blank_rect(w)
|
||||
for i, (ystart, ynum) in enumerate(yl):
|
||||
window_idx = pos + i
|
||||
yield window_idx, xstart, xnum, ystart, ynum
|
||||
col_windows.append(window_idx)
|
||||
pos += rows
|
||||
on_col_done(col_windows)
|
||||
|
||||
def do_layout(self, windows, active_window_idx):
|
||||
n = len(windows)
|
||||
if n == 1:
|
||||
return self.layout_single_window(windows[0])
|
||||
ncols, nrows, special_rows, special_col = self.calc_grid_size(n)
|
||||
|
||||
win_col_map = []
|
||||
|
||||
def on_col_done(col_windows):
|
||||
col_windows = [windows[i] for i in col_windows]
|
||||
win_col_map.append(col_windows)
|
||||
# bottom blank rect
|
||||
self.bottom_blank_rect(col_windows[-1])
|
||||
|
||||
for window_idx, xstart, xnum, ystart, ynum in self.layout_windows(
|
||||
len(windows), nrows, ncols, special_rows, special_col, on_col_done):
|
||||
w = windows[window_idx]
|
||||
w.set_geometry(window_idx, window_geometry(xstart, xnum, ystart, ynum))
|
||||
|
||||
# left, top and right blank rects
|
||||
self.simple_blank_rects(windows[0], windows[-1])
|
||||
@@ -358,23 +598,40 @@ class Grid(Tall):
|
||||
class Vertical(Layout):
|
||||
|
||||
name = 'vertical'
|
||||
main_is_horizontal = False
|
||||
vlayout = Layout.ylayout
|
||||
|
||||
def variable_layout(self, num_windows, biased_map):
|
||||
return self.vlayout(num_windows, bias=variable_bias(num_windows, biased_map) if num_windows else None)
|
||||
|
||||
def remove_all_biases(self):
|
||||
self.biased_map = {}
|
||||
return True
|
||||
|
||||
def apply_bias(self, idx, increment, num_windows, is_horizontal):
|
||||
if self.main_is_horizontal != is_horizontal:
|
||||
return False
|
||||
if num_windows < 2:
|
||||
return False
|
||||
before_layout = list(self.variable_layout(num_windows, self.biased_map))
|
||||
candidate = self.biased_map.copy()
|
||||
before = candidate.get(idx, 0)
|
||||
candidate[idx] = before + increment
|
||||
if before_layout == list(self.variable_layout(num_windows, candidate)):
|
||||
return False
|
||||
self.biased_map = candidate
|
||||
return True
|
||||
|
||||
def do_layout(self, windows, active_window_idx):
|
||||
self.blank_rects = []
|
||||
window_count = len(windows)
|
||||
if window_count == 1:
|
||||
wg = layout_single_window(self.margin_width, self.padding_width)
|
||||
windows[0].set_geometry(0, wg)
|
||||
self.blank_rects = blank_rects_for_window(windows[0])
|
||||
return
|
||||
return self.layout_single_window(windows[0])
|
||||
|
||||
xlayout = self.xlayout(1)
|
||||
xstart, xnum = next(xlayout)
|
||||
ylayout = self.ylayout(window_count)
|
||||
|
||||
for i in range(window_count):
|
||||
ystart, ynum = next(ylayout)
|
||||
windows[i].set_geometry(i, window_geometry(xstart, xnum, ystart, ynum))
|
||||
ylayout = self.variable_layout(window_count, self.biased_map)
|
||||
for i, (w, (ystart, ynum)) in enumerate(zip(windows, ylayout)):
|
||||
w.set_geometry(i, window_geometry(xstart, xnum, ystart, ynum))
|
||||
# bottom blank rect
|
||||
self.bottom_blank_rect(windows[i])
|
||||
|
||||
@@ -382,26 +639,22 @@ class Vertical(Layout):
|
||||
self.simple_blank_rects(windows[0], windows[-1])
|
||||
|
||||
|
||||
class Horizontal(Layout):
|
||||
class Horizontal(Vertical):
|
||||
|
||||
name = 'horizontal'
|
||||
main_is_horizontal = True
|
||||
vlayout = Layout.xlayout
|
||||
|
||||
def do_layout(self, windows, active_window_idx):
|
||||
self.blank_rects = []
|
||||
window_count = len(windows)
|
||||
if window_count == 1:
|
||||
wg = layout_single_window(self.margin_width, self.padding_width)
|
||||
windows[0].set_geometry(0, wg)
|
||||
self.blank_rects = blank_rects_for_window(windows[0])
|
||||
return
|
||||
return self.layout_single_window(windows[0])
|
||||
|
||||
xlayout = self.xlayout(window_count)
|
||||
xlayout = self.variable_layout(window_count, self.biased_map)
|
||||
ylayout = self.ylayout(1)
|
||||
ystart, ynum = next(ylayout)
|
||||
|
||||
for i in range(window_count):
|
||||
xstart, xnum = next(xlayout)
|
||||
windows[i].set_geometry(i, window_geometry(xstart, xnum, ystart, ynum))
|
||||
for i, (w, (xstart, xnum)) in enumerate(zip(windows, xlayout)):
|
||||
w.set_geometry(i, window_geometry(xstart, xnum, ystart, ynum))
|
||||
if i > 0:
|
||||
# between blank rect
|
||||
self.between_blank_rect(windows[i - 1], windows[i])
|
||||
@@ -410,6 +663,29 @@ class Horizontal(Layout):
|
||||
self.simple_blank_rects(windows[0], windows[-1])
|
||||
# bottom blank rect
|
||||
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
|
||||
# }}}
|
||||
|
||||
|
||||
# Instantiation {{{
|
||||
|
||||
all_layouts = {o.name: o for o in globals().values() if isinstance(o, type) and issubclass(o, Layout) and o is not Layout}
|
||||
|
||||
|
||||
def create_layout_object_for(name, os_window_id, tab_id, margin_width, padding_width, border_width, layout_opts=''):
|
||||
key = name, os_window_id, tab_id, margin_width, padding_width, border_width, layout_opts
|
||||
ans = create_layout_object_for.cache.get(key)
|
||||
if ans is None:
|
||||
name, layout_opts = name.partition(':')[::2]
|
||||
ans = create_layout_object_for.cache[key] = all_layouts[name](os_window_id, tab_id, margin_width, padding_width, border_width, layout_opts)
|
||||
return ans
|
||||
|
||||
|
||||
create_layout_object_for.cache = {}
|
||||
|
||||
|
||||
def evict_cached_layouts(tab_id):
|
||||
remove = [key for key in create_layout_object_for.cache if key[2] == tab_id]
|
||||
for key in remove:
|
||||
del create_layout_object_for.cache[key]
|
||||
|
||||
# }}}
|
||||
|
||||
@@ -149,7 +149,7 @@ set_attribute(LineBuf *self, PyObject *args) {
|
||||
#define set_attribute_doc "set_attribute(which, val) -> Set the attribute on all cells in the line."
|
||||
unsigned int shift, val;
|
||||
if (!PyArg_ParseTuple(args, "II", &shift, &val)) return NULL;
|
||||
if (shift < DECORATION_SHIFT || shift > STRIKE_SHIFT) { PyErr_SetString(PyExc_ValueError, "Unknown attribute"); return NULL; }
|
||||
if (shift < DECORATION_SHIFT || shift > DIM_SHIFT) { PyErr_SetString(PyExc_ValueError, "Unknown attribute"); return NULL; }
|
||||
linebuf_set_attribute(self, shift, val);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -414,7 +414,7 @@ get_line(LineBuf *self, index_type y) {
|
||||
|
||||
static PyObject*
|
||||
as_text(LineBuf *self, PyObject *args) {
|
||||
as_text_generic(args, self, get_line, self->ynum, self->xnum, callback, as_ansi);
|
||||
as_text_generic(args, self, get_line, self->ynum, self->xnum);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -515,7 +515,7 @@ set_attribute(Line *self, PyObject *args) {
|
||||
#define set_attribute_doc "set_attribute(which, val) -> Set the attribute on all cells in the line."
|
||||
unsigned int shift, val;
|
||||
if (!PyArg_ParseTuple(args, "II", &shift, &val)) return NULL;
|
||||
if (shift < DECORATION_SHIFT || shift > STRIKE_SHIFT) { PyErr_SetString(PyExc_ValueError, "Unknown attribute"); return NULL; }
|
||||
if (shift < DECORATION_SHIFT || shift > DIM_SHIFT) { PyErr_SetString(PyExc_ValueError, "Unknown attribute"); return NULL; }
|
||||
set_attribute_on_line(self->cells, shift, val, self->xnum);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ void linebuf_mark_line_dirty(LineBuf *self, index_type y);
|
||||
void linebuf_mark_line_clean(LineBuf *self, index_type y);
|
||||
unsigned int linebuf_char_width_at(LineBuf *self, index_type x, index_type y);
|
||||
void linebuf_refresh_sprite_positions(LineBuf *self);
|
||||
bool historybuf_resize(HistoryBuf *self, index_type lines);
|
||||
void historybuf_add_line(HistoryBuf *self, const Line *line);
|
||||
void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other);
|
||||
void historybuf_init_line(HistoryBuf *self, index_type num, Line *l);
|
||||
@@ -82,14 +81,15 @@ void historybuf_refresh_sprite_positions(HistoryBuf *self);
|
||||
void historybuf_clear(HistoryBuf *self);
|
||||
|
||||
|
||||
#define as_text_generic(args, container, get_line, lines, columns, callback, as_ansi) { \
|
||||
#define as_text_generic(args, container, get_line, lines, columns) { \
|
||||
PyObject *callback; \
|
||||
int as_ansi = 0; \
|
||||
if (!PyArg_ParseTuple(args, "O|p", &callback, &as_ansi)) return NULL; \
|
||||
int as_ansi = 0, insert_wrap_markers = 0; \
|
||||
if (!PyArg_ParseTuple(args, "O|pp", &callback, &as_ansi, &insert_wrap_markers)) return NULL; \
|
||||
PyObject *ret = NULL, *t = NULL; \
|
||||
Py_UCS4 *buf = NULL; \
|
||||
PyObject *nl = PyUnicode_FromString("\n"); \
|
||||
if (nl == NULL) goto end; \
|
||||
PyObject *cr = PyUnicode_FromString("\r"); \
|
||||
if (nl == NULL || cr == NULL) goto end; \
|
||||
if (as_ansi) { \
|
||||
buf = malloc(sizeof(Py_UCS4) * columns * 100); \
|
||||
if (buf == NULL) { PyErr_NoMemory(); goto end; } \
|
||||
@@ -110,9 +110,14 @@ void historybuf_clear(HistoryBuf *self);
|
||||
if (t == NULL) goto end; \
|
||||
ret = PyObject_CallFunctionObjArgs(callback, t, NULL); \
|
||||
Py_DECREF(t); if (ret == NULL) goto end; Py_DECREF(ret); \
|
||||
if (insert_wrap_markers) { \
|
||||
ret = PyObject_CallFunctionObjArgs(callback, cr, NULL); \
|
||||
if (ret == NULL) goto end; \
|
||||
Py_CLEAR(ret); \
|
||||
}\
|
||||
} \
|
||||
end: \
|
||||
Py_CLEAR(nl); free(buf); \
|
||||
Py_CLEAR(nl); Py_CLEAR(cr); free(buf); \
|
||||
if (PyErr_Occurred()) return NULL; \
|
||||
Py_RETURN_NONE; \
|
||||
}
|
||||
|
||||
@@ -39,12 +39,10 @@ def init_graphics():
|
||||
def run_app(opts, args):
|
||||
set_scale(opts.box_drawing_scale)
|
||||
set_options(opts, is_wayland, args.debug_gl, args.debug_font_fallback)
|
||||
if is_macos:
|
||||
from .fast_data_types import macos_change_titlebar_color
|
||||
macos_change_titlebar_color(opts.macos_titlebar_color)
|
||||
with cached_values_for('main') as cached_values:
|
||||
w, h = initial_window_size(opts, cached_values)
|
||||
with cached_values_for(run_app.cached_values_name) as cached_values:
|
||||
w, h = run_app.initial_window_size(opts, cached_values)
|
||||
window_id = create_os_window(w, h, appname, args.name or args.cls or appname, args.cls or appname, load_all_shaders)
|
||||
run_app.first_window_callback(opts, window_id)
|
||||
startup_ctx = init_startup_notification(window_id)
|
||||
show_window(window_id)
|
||||
if not is_wayland and not is_macos: # no window icons on wayland
|
||||
@@ -59,6 +57,11 @@ def run_app(opts, args):
|
||||
boss.destroy()
|
||||
|
||||
|
||||
run_app.cached_values_name = 'main'
|
||||
run_app.first_window_callback = lambda opts, window_id: None
|
||||
run_app.initial_window_size = initial_window_size
|
||||
|
||||
|
||||
def ensure_osx_locale():
|
||||
# Ensure the LANG env var is set. See
|
||||
# https://github.com/kovidgoyal/kitty/issues/90
|
||||
@@ -154,9 +157,6 @@ def _main():
|
||||
return
|
||||
if getattr(args, 'detach', False):
|
||||
detach()
|
||||
if args.cmd:
|
||||
exec(args.cmd)
|
||||
return
|
||||
if args.replay_commands:
|
||||
from kitty.client import main
|
||||
main(args.replay_commands)
|
||||
|
||||
@@ -341,6 +341,9 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
case 119:
|
||||
SET_COLOR(set_dynamic_color);
|
||||
break;
|
||||
case 52:
|
||||
DISPATCH_OSC(clipboard_control);
|
||||
break;
|
||||
default:
|
||||
REPORT_ERROR("Unknown OSC code: %u", code);
|
||||
break;
|
||||
@@ -766,17 +769,28 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
}
|
||||
break;
|
||||
case '@':
|
||||
if (startswith(screen->parser_buf + 1, screen->parser_buf_pos - 2, "kitty-cmd{")) {
|
||||
#define CMD_PREFIX "kitty-cmd{"
|
||||
if (startswith(screen->parser_buf + 1, screen->parser_buf_pos - 2, CMD_PREFIX)) {
|
||||
PyObject *cmd = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + 10, screen->parser_buf_pos - 10);
|
||||
if (cmd != NULL) {
|
||||
REPORT_OSC2(screen_handle_cmd, (char)screen->parser_buf[0], cmd);
|
||||
screen_handle_cmd(screen, cmd);
|
||||
Py_DECREF(cmd);
|
||||
} else PyErr_Clear();
|
||||
#undef CMD_PREFIX
|
||||
#define PRINT_PREFIX "kitty-print|"
|
||||
} else if (startswith(screen->parser_buf + 1, screen->parser_buf_pos - 1, PRINT_PREFIX)) {
|
||||
PyObject *msg = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + sizeof(PRINT_PREFIX), screen->parser_buf_pos - sizeof(PRINT_PREFIX));
|
||||
if (msg != NULL) {
|
||||
REPORT_OSC2(screen_handle_print, (char)screen->parser_buf[0], msg);
|
||||
screen_handle_print(screen, msg);
|
||||
Py_DECREF(msg);
|
||||
} else PyErr_Clear();
|
||||
} else {
|
||||
REPORT_ERROR("Unrecognized DCS @ code: 0x%x", screen->parser_buf[1]);
|
||||
}
|
||||
break;
|
||||
#undef PRINT_PREFIX
|
||||
default:
|
||||
REPORT_ERROR("Unrecognized DCS code: 0x%x", screen->parser_buf[0]);
|
||||
break;
|
||||
@@ -816,7 +830,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) {
|
||||
enum KEYS key = 'a';
|
||||
static GraphicsCommand g;
|
||||
unsigned int i, code;
|
||||
unsigned long lcode;
|
||||
uint64_t lcode;
|
||||
bool is_negative;
|
||||
memset(&g, 0, sizeof(g));
|
||||
static uint8_t payload[4096];
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import json
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import types
|
||||
from functools import partial
|
||||
@@ -40,9 +39,14 @@ will only work if this process is run within an existing kitty window.
|
||||
'''.format, appname=appname)
|
||||
|
||||
|
||||
def do_io(to, send, no_response):
|
||||
def encode_send(send):
|
||||
send = ('@kitty-cmd' + json.dumps(send)).encode('ascii')
|
||||
send = b'\x1bP' + send + b'\x1b\\'
|
||||
return b'\x1bP' + send + b'\x1b\\'
|
||||
|
||||
|
||||
def do_io(to, send, no_response):
|
||||
import socket
|
||||
send = encode_send(send)
|
||||
if to:
|
||||
family, address = parse_address_spec(to)[:2]
|
||||
s = socket.socket(family)
|
||||
@@ -79,8 +83,10 @@ def do_io(to, send, no_response):
|
||||
return response
|
||||
|
||||
|
||||
def main(args):
|
||||
all_commands = tuple(sorted(cmap))
|
||||
all_commands = tuple(sorted(cmap))
|
||||
|
||||
|
||||
def parse_rc_args(args):
|
||||
cmds = (' |G {}|\n {}'.format(cmap[c].name, cmap[c].short_desc) for c in all_commands)
|
||||
msg = (
|
||||
'Control {appname} by sending it commands. Add'
|
||||
@@ -90,7 +96,11 @@ def main(args):
|
||||
'{appname} @ |_ command| -h'
|
||||
).format(appname=appname, cmds='\n'.join(cmds))
|
||||
|
||||
global_opts, items = parse_args(args[1:], global_options_spec, 'command ...', msg, '{} @'.format(appname))
|
||||
return parse_args(args[1:], global_options_spec, 'command ...', msg, '{} @'.format(appname))
|
||||
|
||||
|
||||
def main(args):
|
||||
global_opts, items = parse_rc_args(args)
|
||||
|
||||
if not items:
|
||||
from kitty.shell import main
|
||||
@@ -120,5 +130,8 @@ def main(args):
|
||||
if response.get('tb'):
|
||||
print(response['tb'], file=sys.stderr)
|
||||
raise SystemExit(response['error'])
|
||||
if 'data' in response:
|
||||
print(response['data'])
|
||||
data = response.get('data')
|
||||
if data is not None:
|
||||
if func.string_return_is_error and isinstance(data, str):
|
||||
raise SystemExit(data)
|
||||
print(data)
|
||||
|
||||
8
kitty/rgb.py
generated
8
kitty/rgb.py
generated
@@ -35,6 +35,14 @@ def color_as_int(x):
|
||||
return x.red << 16 | x.green << 8 | x.blue
|
||||
|
||||
|
||||
def color_as_sharp(x):
|
||||
return '#{:02x}{:02x}{:02x}'.format(*x)
|
||||
|
||||
|
||||
def color_as_sgr(x):
|
||||
return ':2:{}:{}:{}'.format(*x)
|
||||
|
||||
|
||||
def to_color(raw, validate=False):
|
||||
# See man XParseColor
|
||||
x = raw.strip().lower()
|
||||
|
||||
144
kitty/screen.c
144
kitty/screen.c
@@ -212,12 +212,6 @@ screen_rescale_images(Screen *self, unsigned int old_cell_width, unsigned int ol
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
screen_change_scrollback_size(Screen *self, unsigned int size) {
|
||||
if (size != self->historybuf->ynum) return historybuf_resize(self->historybuf, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
reset_callbacks(Screen *self, PyObject *a UNUSED) {
|
||||
Py_CLEAR(self->callbacks);
|
||||
@@ -452,15 +446,21 @@ write_escape_code_to_child(Screen *self, unsigned char which, const char *data)
|
||||
if (suffix[0]) write_to_child(self, suffix, strlen(suffix));
|
||||
}
|
||||
|
||||
static inline bool
|
||||
cursor_within_margins(Screen *self) {
|
||||
return self->margin_top <= self->cursor->y && self->cursor->y >= self->margin_bottom;
|
||||
}
|
||||
|
||||
void
|
||||
screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const uint8_t *payload) {
|
||||
unsigned int x = self->cursor->x, y = self->cursor->y;
|
||||
const char *response = grman_handle_command(self->grman, cmd, payload, self->cursor, &self->is_dirty);
|
||||
if (response != NULL) write_escape_code_to_child(self, APC, response);
|
||||
if (x != self->cursor->x || y != self->cursor->y) {
|
||||
bool in_margins = cursor_within_margins(self);
|
||||
if (self->cursor->x >= self->columns) { self->cursor->x = 0; self->cursor->y++; }
|
||||
if (self->cursor->y > self->margin_bottom) screen_scroll(self, self->cursor->y - self->margin_bottom);
|
||||
screen_ensure_bounds(self, false);
|
||||
screen_ensure_bounds(self, false, in_margins);
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
@@ -664,7 +664,7 @@ screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=
|
||||
if (count == 0) count = 1;
|
||||
if (move_direction < 0 && count > self->cursor->x) self->cursor->x = 0;
|
||||
else self->cursor->x += move_direction * count;
|
||||
screen_ensure_bounds(self, false);
|
||||
screen_ensure_bounds(self, false, cursor_within_margins(self));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -674,10 +674,11 @@ screen_cursor_forward(Screen *self, unsigned int count/*=1*/) {
|
||||
|
||||
void
|
||||
screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/) {
|
||||
bool in_margins = cursor_within_margins(self);
|
||||
if (count == 0) count = 1;
|
||||
if (move_direction < 0 && count > self->cursor->y) self->cursor->y = 0;
|
||||
else self->cursor->y += move_direction * count;
|
||||
screen_ensure_bounds(self, true);
|
||||
screen_ensure_bounds(self, true, in_margins);
|
||||
if (do_carriage_return) self->cursor->x = 0;
|
||||
}
|
||||
|
||||
@@ -701,7 +702,7 @@ screen_cursor_to_column(Screen *self, unsigned int column) {
|
||||
unsigned int x = MAX(column, 1) - 1;
|
||||
if (x != self->cursor->x) {
|
||||
self->cursor->x = x;
|
||||
screen_ensure_bounds(self, false);
|
||||
screen_ensure_bounds(self, false, cursor_within_margins(self));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -781,9 +782,10 @@ screen_carriage_return(Screen *self) {
|
||||
|
||||
void
|
||||
screen_linefeed(Screen *self) {
|
||||
bool in_margins = cursor_within_margins(self);
|
||||
screen_index(self);
|
||||
if (self->modes.mLNM) screen_carriage_return(self);
|
||||
screen_ensure_bounds(self, false);
|
||||
screen_ensure_bounds(self, false, in_margins);
|
||||
}
|
||||
|
||||
#define buffer_push(self, ans) { \
|
||||
@@ -844,7 +846,7 @@ screen_restore_cursor(Screen *self) {
|
||||
set_mode_from_const(self, DECAWM, sp->mDECAWM);
|
||||
set_mode_from_const(self, DECSCNM, sp->mDECSCNM);
|
||||
cursor_copy_to(&(sp->cursor), self->cursor);
|
||||
screen_ensure_bounds(self, false);
|
||||
screen_ensure_bounds(self, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -862,9 +864,9 @@ screen_restore_modes(Screen *self) {
|
||||
}
|
||||
|
||||
void
|
||||
screen_ensure_bounds(Screen *self, bool force_use_margins/*=false*/) {
|
||||
screen_ensure_bounds(Screen *self, bool force_use_margins/*=false*/, bool in_margins) {
|
||||
unsigned int top, bottom;
|
||||
if (force_use_margins || self->modes.mDECOM) {
|
||||
if (in_margins && (force_use_margins || self->modes.mDECOM)) {
|
||||
top = self->margin_top; bottom = self->margin_bottom;
|
||||
} else {
|
||||
top = 0; bottom = self->lines - 1;
|
||||
@@ -875,6 +877,7 @@ screen_ensure_bounds(Screen *self, bool force_use_margins/*=false*/) {
|
||||
|
||||
void
|
||||
screen_cursor_position(Screen *self, unsigned int line, unsigned int column) {
|
||||
bool in_margins = cursor_within_margins(self);
|
||||
line = (line == 0 ? 1 : line) - 1;
|
||||
column = (column == 0 ? 1: column) - 1;
|
||||
if (self->modes.mDECOM) {
|
||||
@@ -882,7 +885,7 @@ screen_cursor_position(Screen *self, unsigned int line, unsigned int column) {
|
||||
line = MAX(self->margin_top, MIN(line, self->margin_bottom));
|
||||
}
|
||||
self->cursor->x = column; self->cursor->y = line;
|
||||
screen_ensure_bounds(self, false);
|
||||
screen_ensure_bounds(self, false, in_margins);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -939,7 +942,7 @@ void
|
||||
screen_erase_in_display(Screen *self, unsigned int how, bool private) {
|
||||
/* Erases display in a specific way.
|
||||
|
||||
:param int how: defines the way the line should be erased in:
|
||||
:param int how: defines the way the screen should be erased:
|
||||
|
||||
* ``0`` -- Erases from cursor to end of screen, including
|
||||
cursor position.
|
||||
@@ -1077,6 +1080,7 @@ void
|
||||
screen_bell(Screen *self) {
|
||||
request_window_attention(self->window_id, OPT(enable_audio_bell));
|
||||
if (OPT(visual_bell_duration) > 0.0f) self->start_visual_bell_at = monotonic();
|
||||
CALLBACK("on_bell", NULL);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1199,6 +1203,11 @@ set_dynamic_color(Screen *self, unsigned int code, PyObject *color) {
|
||||
else { CALLBACK("set_dynamic_color", "IO", code, color); }
|
||||
}
|
||||
|
||||
void
|
||||
clipboard_control(Screen *self, PyObject *data) {
|
||||
CALLBACK("clipboard_control", "O", data);
|
||||
}
|
||||
|
||||
void
|
||||
set_color_table_color(Screen *self, unsigned int code, PyObject *color) {
|
||||
if (color == NULL) { CALLBACK("set_color_table_color", "Is", code, ""); }
|
||||
@@ -1210,6 +1219,11 @@ screen_handle_cmd(Screen *self, PyObject *cmd) {
|
||||
CALLBACK("handle_remote_cmd", "O", cmd);
|
||||
}
|
||||
|
||||
void
|
||||
screen_handle_print(Screen *self, PyObject *msg) {
|
||||
CALLBACK("handle_remote_print", "O", msg);
|
||||
}
|
||||
|
||||
void
|
||||
screen_request_capabilities(Screen *self, char c, PyObject *q) {
|
||||
static char buf[128];
|
||||
@@ -1462,7 +1476,12 @@ screen_open_url(Screen *self) {
|
||||
|
||||
static PyObject*
|
||||
as_text(Screen *self, PyObject *args) {
|
||||
as_text_generic(args, self, visual_line_, self->lines, self->columns, callback, as_ansi);
|
||||
as_text_generic(args, self, visual_line_, self->lines, self->columns);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
as_text_non_visual(Screen *self, PyObject *args) {
|
||||
as_text_generic(args, self, range_line_, self->lines, self->columns);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
@@ -1516,6 +1535,52 @@ screen_wcswidth(PyObject UNUSED *self, PyObject *str) {
|
||||
return PyLong_FromUnsignedLong(ans);
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
screen_truncate_point_for_length(PyObject UNUSED *self, PyObject *args) {
|
||||
PyObject *str; unsigned int num_cells, start_pos = 0;
|
||||
if (!PyArg_ParseTuple(args, "OI|I", &str, &num_cells, &start_pos)) return NULL;
|
||||
if (PyUnicode_READY(str) != 0) return NULL;
|
||||
int kind = PyUnicode_KIND(str);
|
||||
void *data = PyUnicode_DATA(str);
|
||||
Py_ssize_t len = PyUnicode_GET_LENGTH(str), i;
|
||||
char_type prev_ch = 0;
|
||||
int prev_width = 0;
|
||||
bool in_sgr = false;
|
||||
unsigned long width_so_far = 0;
|
||||
for (i = start_pos; i < len && width_so_far < num_cells; i++) {
|
||||
char_type ch = PyUnicode_READ(kind, data, i);
|
||||
if (in_sgr) {
|
||||
if (ch == 'm') in_sgr = false;
|
||||
continue;
|
||||
}
|
||||
if (ch == 0x1b && i + 1 < len && PyUnicode_READ(kind, data, i + 1) == '[') { in_sgr = true; continue; }
|
||||
if (ch == 0xfe0f) {
|
||||
if (is_emoji_presentation_base(prev_ch) && prev_width == 1) {
|
||||
width_so_far += 1;
|
||||
prev_width = 2;
|
||||
} else prev_width = 0;
|
||||
} else {
|
||||
int w = wcwidth_std(ch);
|
||||
switch(w) {
|
||||
case -1:
|
||||
case 0:
|
||||
prev_width = 0; break;
|
||||
case 2:
|
||||
prev_width = 2; break;
|
||||
default:
|
||||
prev_width = 1; break;
|
||||
}
|
||||
if (width_so_far + prev_width > num_cells) { break; }
|
||||
width_so_far += prev_width;
|
||||
}
|
||||
prev_ch = ch;
|
||||
|
||||
}
|
||||
return PyLong_FromUnsignedLong(i);
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
line(Screen *self, PyObject *val) {
|
||||
unsigned long y = PyLong_AsUnsignedLong(val);
|
||||
@@ -1633,19 +1698,11 @@ static PyObject*
|
||||
start_selection(Screen *self, PyObject *args) {
|
||||
unsigned int x, y;
|
||||
int rectangle_select = 0, extend_mode = EXTEND_CELL;
|
||||
if (!PyArg_ParseTuple(args, "II|pp", &x, &y, &rectangle_select, &extend_mode)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "II|pi", &x, &y, &rectangle_select, &extend_mode)) return NULL;
|
||||
screen_start_selection(self, x, y, rectangle_select, extend_mode);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
change_scrollback_size(Screen *self, PyObject *args) {
|
||||
unsigned int count = 1;
|
||||
if (!PyArg_ParseTuple(args, "|I", &count)) return NULL;
|
||||
if (!screen_change_scrollback_size(self, MAX(self->lines, count))) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
text_for_selection(Screen *self, PyObject *a UNUSED) {
|
||||
FullSelectionBoundary start, end;
|
||||
@@ -1801,19 +1858,29 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool ended) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EXTEND_LINE:
|
||||
if (extending_leftwards) {
|
||||
found = screen_selection_range_for_line(self, self->selection.end_y, &start, &end);
|
||||
if (found) { self->selection.end_x = start; }
|
||||
found = screen_selection_range_for_line(self, self->selection.start_y, &start, &end);
|
||||
if (found) { self->selection.start_x = end; }
|
||||
} else {
|
||||
found = screen_selection_range_for_line(self, self->selection.start_y, &start, &end);
|
||||
if (found) { self->selection.start_x = start; }
|
||||
found = screen_selection_range_for_line(self, self->selection.end_y, &start, &end);
|
||||
if (found) { self->selection.end_x = end; }
|
||||
case EXTEND_LINE: {
|
||||
index_type top_line = extending_leftwards ? self->selection.end_y : self->selection.start_y;
|
||||
index_type bottom_line = extending_leftwards ? self->selection.start_y : self->selection.end_y;
|
||||
while(top_line > 0 && visual_line_(self, top_line)->continued) top_line--;
|
||||
while(bottom_line < self->lines - 1 && visual_line_(self, bottom_line + 1)->continued) bottom_line++;
|
||||
found = screen_selection_range_for_line(self, top_line, &start, &end);
|
||||
if (found) {
|
||||
if (extending_leftwards) {
|
||||
self->selection.end_x = start; self->selection.end_y = top_line;
|
||||
} else {
|
||||
self->selection.start_x = start; self->selection.start_y = top_line;
|
||||
}
|
||||
}
|
||||
found = screen_selection_range_for_line(self, bottom_line, &start, &end);
|
||||
if (found) {
|
||||
if (extending_leftwards) {
|
||||
self->selection.start_x = end; self->selection.start_y = bottom_line;
|
||||
} else {
|
||||
self->selection.end_x = end; self->selection.end_y = bottom_line;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EXTEND_CELL:
|
||||
break;
|
||||
}
|
||||
@@ -1903,7 +1970,6 @@ static PyMethodDef methods[] = {
|
||||
MND(delete_lines, METH_VARARGS)
|
||||
MND(insert_characters, METH_VARARGS)
|
||||
MND(delete_characters, METH_VARARGS)
|
||||
MND(change_scrollback_size, METH_VARARGS)
|
||||
MND(erase_characters, METH_VARARGS)
|
||||
MND(cursor_up, METH_VARARGS)
|
||||
MND(cursor_up1, METH_VARARGS)
|
||||
@@ -1912,6 +1978,7 @@ static PyMethodDef methods[] = {
|
||||
MND(cursor_forward, METH_VARARGS)
|
||||
{"index", (PyCFunction)xxx_index, METH_VARARGS, ""},
|
||||
MND(as_text, METH_VARARGS)
|
||||
MND(as_text_non_visual, METH_VARARGS)
|
||||
MND(refresh_sprite_positions, METH_NOARGS)
|
||||
MND(tab, METH_NOARGS)
|
||||
MND(backspace, METH_NOARGS)
|
||||
@@ -1987,6 +2054,7 @@ PyTypeObject Screen_Type = {
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
|
||||
{"wcswidth", (PyCFunction)screen_wcswidth, METH_O, ""},
|
||||
{"truncate_point_for_length", (PyCFunction)screen_truncate_point_for_length, METH_VARARGS, ""},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ void screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_directi
|
||||
void screen_erase_in_line(Screen *, unsigned int, bool);
|
||||
void screen_erase_in_display(Screen *, unsigned int, bool);
|
||||
void screen_draw(Screen *screen, uint32_t codepoint);
|
||||
void screen_ensure_bounds(Screen *self, bool use_margins);
|
||||
void screen_ensure_bounds(Screen *self, bool use_margins, bool cursor_was_within_margins);
|
||||
void screen_toggle_screen_buffer(Screen *self);
|
||||
void screen_normal_keypad_mode(Screen *self);
|
||||
void screen_alternate_keypad_mode(Screen *self);
|
||||
@@ -138,11 +138,13 @@ void screen_erase_characters(Screen *self, unsigned int count);
|
||||
void screen_set_margins(Screen *self, unsigned int top, unsigned int bottom);
|
||||
void screen_change_charset(Screen *, uint32_t to);
|
||||
void screen_handle_cmd(Screen *, PyObject *cmd);
|
||||
void screen_handle_print(Screen *, PyObject *cmd);
|
||||
void screen_designate_charset(Screen *, uint32_t which, uint32_t as);
|
||||
void screen_use_latin1(Screen *, bool);
|
||||
void set_title(Screen *self, PyObject*);
|
||||
void set_icon(Screen *self, PyObject*);
|
||||
void set_dynamic_color(Screen *self, unsigned int code, PyObject*);
|
||||
void clipboard_control(Screen *self, PyObject*);
|
||||
void set_color_table_color(Screen *self, unsigned int code, PyObject*);
|
||||
uint32_t* translation_table(uint32_t which);
|
||||
void screen_request_capabilities(Screen *, char, PyObject *);
|
||||
|
||||
@@ -110,12 +110,7 @@ def create_session(opts, args=None, special_window=None, cwd_from=None, respect_
|
||||
with open(args.session) as f:
|
||||
return parse_session(f.read(), opts)
|
||||
ans = Session()
|
||||
if args and args.window_layout:
|
||||
if args.window_layout not in opts.enabled_layouts:
|
||||
opts.enabled_layouts.insert(0, args.window_layout)
|
||||
current_layout = args.window_layout
|
||||
else:
|
||||
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
|
||||
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
|
||||
ans.add_tab(opts)
|
||||
ans.tabs[-1].layout = current_layout
|
||||
if special_window is None:
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "gl.h"
|
||||
#include "fonts.h"
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, CURSOR_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, BLIT_PROGRAM, NUM_PROGRAMS };
|
||||
enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BLIT_UNIT };
|
||||
@@ -395,9 +394,10 @@ set_cell_uniforms(float current_inactive_text_alpha) {
|
||||
cell_uniform_data.gpploc = glGetUniformLocation(program_id(GRAPHICS_PREMULT_PROGRAM), "inactive_text_alpha");
|
||||
cell_uniform_data.cploc = glGetUniformLocation(program_id(CELL_PROGRAM), "inactive_text_alpha");
|
||||
cell_uniform_data.cfploc = glGetUniformLocation(program_id(CELL_FG_PROGRAM), "inactive_text_alpha");
|
||||
#define S(prog, name, val) { bind_program(prog); glUniform1i(glGetUniformLocation(program_id(prog), #name), val); }
|
||||
S(GRAPHICS_PROGRAM, image, GRAPHICS_UNIT); S(GRAPHICS_PREMULT_PROGRAM, image, GRAPHICS_UNIT);
|
||||
S(CELL_PROGRAM, sprites, SPRITE_MAP_UNIT); S(CELL_FG_PROGRAM, sprites, SPRITE_MAP_UNIT);
|
||||
#define S(prog, name, val, type) { bind_program(prog); glUniform##type(glGetUniformLocation(program_id(prog), #name), val); }
|
||||
S(GRAPHICS_PROGRAM, image, GRAPHICS_UNIT, 1i); S(GRAPHICS_PREMULT_PROGRAM, image, GRAPHICS_UNIT, 1i);
|
||||
S(CELL_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i); S(CELL_FG_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i);
|
||||
S(CELL_PROGRAM, dim_opacity, OPT(dim_opacity), 1f); S(CELL_FG_PROGRAM, dim_opacity, OPT(dim_opacity), 1f);
|
||||
#undef S
|
||||
cell_uniform_data.constants_set = true;
|
||||
}
|
||||
@@ -487,7 +487,7 @@ draw_cursor(CursorRenderInfo *cursor, bool is_focused) {
|
||||
// }}}
|
||||
|
||||
// Borders {{{
|
||||
enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_default_bg, BORDER_active_border_color, BORDER_inactive_border_color, NUM_BORDER_UNIFORMS };
|
||||
enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_default_bg, BORDER_active_border_color, BORDER_inactive_border_color, BORDER_bell_border_color, NUM_BORDER_UNIFORMS };
|
||||
static GLint border_uniform_locations[NUM_BORDER_UNIFORMS] = {0};
|
||||
|
||||
static void
|
||||
@@ -501,6 +501,7 @@ init_borders_program() {
|
||||
else if SET_LOC(default_bg);
|
||||
else if SET_LOC(active_border_color);
|
||||
else if SET_LOC(inactive_border_color);
|
||||
else if SET_LOC(bell_border_color);
|
||||
else { fatal("Unknown uniform in borders program: %s", p->uniforms[i].name); return; }
|
||||
}
|
||||
if (left) { fatal("Left over uniforms in borders program"); return; }
|
||||
@@ -537,6 +538,7 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
|
||||
glUniform1f(border_uniform_locations[BORDER_background_opacity], OPT(background_opacity));
|
||||
glUniform3f(border_uniform_locations[BORDER_active_border_color], CV3(OPT(active_border_color)));
|
||||
glUniform3f(border_uniform_locations[BORDER_inactive_border_color], CV3(OPT(inactive_border_color)));
|
||||
glUniform3f(border_uniform_locations[BORDER_bell_border_color], CV3(OPT(bell_border_color)));
|
||||
}
|
||||
glUniform2ui(border_uniform_locations[BORDER_viewport], viewport_width, viewport_height);
|
||||
color_type default_bg = num_visible_windows > 1 ? OPT(background) : active_window_bg;
|
||||
|
||||
@@ -14,12 +14,24 @@ from .cli import (
|
||||
emph, green, italic, parse_option_spec, print_help_for_seq, title
|
||||
)
|
||||
from .cmds import cmap, display_subcommand_help, parse_subcommand_cli
|
||||
from .constants import cache_dir, version
|
||||
from .constants import cache_dir, is_macos, version
|
||||
|
||||
all_commands = tuple(sorted(cmap))
|
||||
match_commands = tuple(sorted(all_commands + ('exit', 'help', 'quit')))
|
||||
|
||||
|
||||
def init_readline(readline):
|
||||
try:
|
||||
readline.read_init_file()
|
||||
except EnvironmentError:
|
||||
if not is_macos:
|
||||
raise
|
||||
if 'libedit' in readline.__doc__:
|
||||
readline.parse_and_bind("bind ^I rl_complete")
|
||||
else:
|
||||
readline.parse_and_bind('tab: complete')
|
||||
|
||||
|
||||
def cmd_names_matching(prefix):
|
||||
for cmd in match_commands:
|
||||
if not prefix or cmd.startswith(prefix):
|
||||
@@ -76,7 +88,6 @@ class Completer:
|
||||
if os.path.exists(self.history_path):
|
||||
readline.read_history_file(self.history_path)
|
||||
readline.set_completer(self.complete)
|
||||
readline.parse_and_bind('tab: complete')
|
||||
delims = readline.get_completer_delims()
|
||||
readline.set_completer_delims(delims.replace('-', ''))
|
||||
return self
|
||||
@@ -141,7 +152,7 @@ def run_cmd(global_opts, cmd, func, opts, items):
|
||||
|
||||
|
||||
def real_main(global_opts):
|
||||
readline.read_init_file()
|
||||
init_readline(readline)
|
||||
print_help_for_seq.allow_pager = False
|
||||
print('Welcome to the kitty shell!')
|
||||
print('Use {} for assistance or {} to quit'.format(green('help'), green('exit')))
|
||||
|
||||
@@ -303,6 +303,21 @@ repaint_delay(PyObject *val) {
|
||||
PyObject *key, *value; Py_ssize_t pos = 0; \
|
||||
while (PyDict_Next(d, &pos, &key, &value))
|
||||
|
||||
static int kitty_mod = 0;
|
||||
|
||||
static inline int
|
||||
resolve_mods(int mods) {
|
||||
if (mods & GLFW_MOD_KITTY) {
|
||||
mods = (mods & ~GLFW_MOD_KITTY) | kitty_mod;
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
|
||||
static int
|
||||
convert_mods(PyObject *obj) {
|
||||
return resolve_mods(PyLong_AsLong(obj));
|
||||
}
|
||||
|
||||
static inline void
|
||||
set_special_keys(PyObject *dict) {
|
||||
dict_iter(dict) {
|
||||
@@ -313,6 +328,10 @@ set_special_keys(PyObject *dict) {
|
||||
}}
|
||||
}
|
||||
|
||||
PYWRAP0(next_window_id) {
|
||||
return PyLong_FromUnsignedLongLong(global_state.window_id_counter + 1);
|
||||
}
|
||||
|
||||
PYWRAP1(handle_for_window_id) {
|
||||
id_type os_window_id;
|
||||
PA("K", &os_window_id);
|
||||
@@ -332,12 +351,15 @@ PYWRAP1(set_options) {
|
||||
global_state.debug_font_fallback = debug_font_fallback ? true : false;
|
||||
#define GA(name) ret = PyObject_GetAttrString(opts, #name); if (ret == NULL) return NULL;
|
||||
#define S(name, convert) { GA(name); global_state.opts.name = convert(ret); Py_DECREF(ret); if (PyErr_Occurred()) return NULL; }
|
||||
GA(kitty_mod);
|
||||
kitty_mod = PyLong_AsLong(ret); Py_CLEAR(ret); if (PyErr_Occurred()) return NULL;
|
||||
S(visual_bell_duration, PyFloat_AsDouble);
|
||||
S(enable_audio_bell, PyObject_IsTrue);
|
||||
S(focus_follows_mouse, PyObject_IsTrue);
|
||||
S(cursor_blink_interval, PyFloat_AsDouble);
|
||||
S(cursor_stop_blinking_after, PyFloat_AsDouble);
|
||||
S(background_opacity, PyFloat_AsDouble);
|
||||
S(dim_opacity, PyFloat_AsDouble);
|
||||
S(inactive_text_alpha, PyFloat_AsDouble);
|
||||
S(window_padding_width, PyFloat_AsDouble);
|
||||
S(cursor_shape, PyLong_AsLong);
|
||||
@@ -345,19 +367,22 @@ PYWRAP1(set_options) {
|
||||
S(tab_bar_edge, PyLong_AsLong);
|
||||
S(mouse_hide_wait, PyFloat_AsDouble);
|
||||
S(wheel_scroll_multiplier, PyFloat_AsDouble);
|
||||
S(open_url_modifiers, PyLong_AsUnsignedLong);
|
||||
S(rectangle_select_modifiers, PyLong_AsUnsignedLong);
|
||||
S(open_url_modifiers, convert_mods);
|
||||
S(rectangle_select_modifiers, convert_mods);
|
||||
S(click_interval, PyFloat_AsDouble);
|
||||
S(url_color, color_as_int);
|
||||
S(background, color_as_int);
|
||||
S(active_border_color, color_as_int);
|
||||
S(inactive_border_color, color_as_int);
|
||||
S(bell_border_color, color_as_int);
|
||||
S(repaint_delay, repaint_delay);
|
||||
S(input_delay, repaint_delay);
|
||||
S(sync_to_monitor, PyObject_IsTrue);
|
||||
S(close_on_child_death, PyObject_IsTrue);
|
||||
S(window_alert_on_bell, PyObject_IsTrue);
|
||||
S(macos_option_as_alt, PyObject_IsTrue);
|
||||
S(macos_hide_titlebar, PyObject_IsTrue);
|
||||
S(macos_hide_from_tasks, PyObject_IsTrue);
|
||||
|
||||
PyObject *chars = PyObject_GetAttrString(opts, "select_by_word_characters");
|
||||
if (chars == NULL) return NULL;
|
||||
@@ -459,6 +484,25 @@ PYWRAP1(mark_os_window_for_close) {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
PYWRAP1(set_titlebar_color) {
|
||||
id_type os_window_id;
|
||||
unsigned int color;
|
||||
PA("KI", &os_window_id, &color);
|
||||
WITH_OS_WINDOW(os_window_id)
|
||||
set_titlebar_color(os_window, color);
|
||||
Py_RETURN_TRUE;
|
||||
END_WITH_OS_WINDOW
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
PYWRAP1(mark_tab_bar_dirty) {
|
||||
id_type os_window_id = PyLong_AsUnsignedLongLong(args);
|
||||
WITH_OS_WINDOW(os_window_id)
|
||||
os_window->tab_bar_data_updated = false;
|
||||
END_WITH_OS_WINDOW
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
fix_window_idx(Tab *tab, id_type window_id, unsigned int *window_idx) {
|
||||
for (id_type fix = 0; fix < tab->num_windows; fix++) {
|
||||
@@ -545,6 +589,7 @@ PYWRAP1(set_display_state) {
|
||||
|
||||
THREE_ID_OBJ(update_window_title)
|
||||
THREE_ID(remove_window)
|
||||
PYWRAP1(resolve_key_mods) { int mods; PA("ii", &kitty_mod, &mods); return PyLong_FromLong(resolve_mods(mods)); }
|
||||
PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); }
|
||||
PYWRAP1(add_window) { PyObject *title; id_type a, b; PA("KKO", &a, &b, &title); return PyLong_FromUnsignedLongLong(add_window(a, b, title)); }
|
||||
PYWRAP0(current_os_window) { OSWindow *w = current_os_window(); if (!w) Py_RETURN_NONE; return PyLong_FromUnsignedLongLong(w->id); }
|
||||
@@ -560,8 +605,10 @@ KK5I(add_borders_rect)
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
MW(current_os_window, METH_NOARGS),
|
||||
MW(next_window_id, METH_NOARGS),
|
||||
MW(set_options, METH_VARARGS),
|
||||
MW(set_in_sequence_mode, METH_O),
|
||||
MW(resolve_key_mods, METH_VARARGS),
|
||||
MW(handle_for_window_id, METH_VARARGS),
|
||||
MW(set_logical_dpi, METH_VARARGS),
|
||||
MW(pt_to_px, METH_O),
|
||||
@@ -581,6 +628,8 @@ static PyMethodDef module_methods[] = {
|
||||
MW(set_window_render_data, METH_VARARGS),
|
||||
MW(viewport_for_window, METH_VARARGS),
|
||||
MW(mark_os_window_for_close, METH_VARARGS),
|
||||
MW(set_titlebar_color, METH_VARARGS),
|
||||
MW(mark_tab_bar_dirty, METH_O),
|
||||
MW(update_window_visibility, METH_VARARGS),
|
||||
MW(set_boss, METH_O),
|
||||
MW(set_display_state, METH_VARARGS),
|
||||
|
||||
@@ -20,18 +20,19 @@ typedef struct {
|
||||
unsigned int rectangle_select_modifiers;
|
||||
unsigned int url_style;
|
||||
char_type select_by_word_characters[256]; size_t select_by_word_characters_count;
|
||||
color_type url_color, background, active_border_color, inactive_border_color;
|
||||
color_type url_color, background, active_border_color, inactive_border_color, bell_border_color;
|
||||
double repaint_delay, input_delay;
|
||||
bool focus_follows_mouse;
|
||||
bool macos_option_as_alt, macos_hide_titlebar;
|
||||
bool macos_option_as_alt, macos_hide_titlebar, macos_hide_from_tasks;
|
||||
int adjust_line_height_px, adjust_column_width_px;
|
||||
float adjust_line_height_frac, adjust_column_width_frac;
|
||||
float background_opacity;
|
||||
float background_opacity, dim_opacity;
|
||||
float inactive_text_alpha;
|
||||
float window_padding_width;
|
||||
Edge tab_bar_edge;
|
||||
bool sync_to_monitor;
|
||||
bool close_on_child_death;
|
||||
bool window_alert_on_bell;
|
||||
} Options;
|
||||
|
||||
typedef struct {
|
||||
@@ -104,6 +105,7 @@ typedef struct {
|
||||
unsigned int active_tab, num_tabs, capacity, last_active_tab, last_num_tabs, last_active_window_id;
|
||||
bool focused_at_last_render, needs_render;
|
||||
ScreenRenderData tab_bar_render_data;
|
||||
bool tab_bar_data_updated;
|
||||
bool is_focused;
|
||||
double cursor_blink_zero_time, last_mouse_activity_at;
|
||||
double mouse_x, mouse_y;
|
||||
@@ -115,6 +117,7 @@ typedef struct {
|
||||
bool has_pending_resizes, is_semi_transparent, shown_once, is_damaged;
|
||||
uint32_t offscreen_texture_id;
|
||||
unsigned int clear_count;
|
||||
color_type last_titlebar_color;
|
||||
} OSWindow;
|
||||
|
||||
|
||||
@@ -179,3 +182,4 @@ void update_surface_size(int, int, uint32_t);
|
||||
void free_texture(uint32_t*);
|
||||
void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool);
|
||||
void send_sprite_to_gpu(unsigned int, unsigned int, unsigned int, pixel*);
|
||||
void set_titlebar_color(OSWindow *w, color_type color);
|
||||
|
||||
153
kitty/tabs.py
153
kitty/tabs.py
@@ -11,16 +11,16 @@ from .child import Child
|
||||
from .config import build_ansi_color_table
|
||||
from .constants import WindowGeometry, appname, get_boss, is_macos, is_wayland
|
||||
from .fast_data_types import (
|
||||
DECAWM, Screen, add_tab, glfw_post_empty_event, remove_tab, remove_window,
|
||||
set_active_tab, set_tab_bar_render_data, swap_tabs, viewport_for_window,
|
||||
x11_window_id
|
||||
DECAWM, Screen, add_tab, glfw_post_empty_event, mark_tab_bar_dirty,
|
||||
next_window_id, pt_to_px, remove_tab, remove_window, set_active_tab,
|
||||
set_tab_bar_render_data, swap_tabs, viewport_for_window, x11_window_id
|
||||
)
|
||||
from .layout import Rect, all_layouts
|
||||
from .layout import Rect, create_layout_object_for, evict_cached_layouts
|
||||
from .session import resolved_shell
|
||||
from .utils import color_as_int, log_error
|
||||
from .window import Window, calculate_gl_geometry
|
||||
|
||||
TabbarData = namedtuple('TabbarData', 'title is_active is_last')
|
||||
TabbarData = namedtuple('TabbarData', 'title is_active is_last needs_attention')
|
||||
SpecialWindowInstance = namedtuple('SpecialWindow', 'cmd stdin override_title cwd_from cwd overlay_for env')
|
||||
|
||||
|
||||
@@ -31,17 +31,19 @@ def SpecialWindow(cmd, stdin=None, override_title=None, cwd_from=None, cwd=None,
|
||||
class Tab: # {{{
|
||||
|
||||
def __init__(self, tab_manager, session_tab=None, special_window=None, cwd_from=None):
|
||||
self._active_window_idx = 0
|
||||
self.tab_manager_ref = weakref.ref(tab_manager)
|
||||
self.os_window_id = tab_manager.os_window_id
|
||||
self.id = add_tab(self.os_window_id)
|
||||
if not self.id:
|
||||
raise Exception('No OS window with id {} found, or tab counter has wrapped'.format(self.os_window_id))
|
||||
self.opts, self.args = tab_manager.opts, tab_manager.args
|
||||
self.margin_width, self.padding_width = map(pt_to_px, (
|
||||
self.opts.window_margin_width, self.opts.window_padding_width))
|
||||
self.name = getattr(session_tab, 'name', '')
|
||||
self.enabled_layouts = [x.lower() for x in getattr(session_tab, 'enabled_layouts', None) or self.opts.enabled_layouts]
|
||||
self.borders = Borders(self.os_window_id, self.id, self.opts)
|
||||
self.windows = deque()
|
||||
self.active_window_idx = 0
|
||||
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
|
||||
setattr(self, which + '_window', partial(self.nth_window, num=i))
|
||||
if session_tab is None:
|
||||
@@ -66,6 +68,31 @@ class Tab: # {{{
|
||||
self.new_window(cmd=cmd)
|
||||
self.set_active_window_idx(session_tab.active_window_idx)
|
||||
|
||||
@property
|
||||
def active_window_idx(self):
|
||||
return self._active_window_idx
|
||||
|
||||
@active_window_idx.setter
|
||||
def active_window_idx(self, val):
|
||||
try:
|
||||
old_active_window = self.windows[self._active_window_idx]
|
||||
except Exception:
|
||||
old_active_window = None
|
||||
self._active_window_idx = max(0, min(val, len(self.windows) - 1))
|
||||
try:
|
||||
new_active_window = self.windows[self._active_window_idx]
|
||||
except Exception:
|
||||
new_active_window = None
|
||||
if old_active_window is not new_active_window:
|
||||
if old_active_window is not None:
|
||||
old_active_window.focus_changed(False)
|
||||
if new_active_window is not None:
|
||||
new_active_window.focus_changed(True)
|
||||
tm = self.tab_manager_ref()
|
||||
if tm is not None:
|
||||
self.relayout_borders()
|
||||
tm.mark_tab_bar_dirty()
|
||||
|
||||
@property
|
||||
def active_window(self):
|
||||
return self.windows[self.active_window_idx] if self.windows else None
|
||||
@@ -78,13 +105,19 @@ class Tab: # {{{
|
||||
self.name = title or ''
|
||||
tm = self.tab_manager_ref()
|
||||
if tm is not None:
|
||||
tm.title_changed(self.name)
|
||||
tm.mark_tab_bar_dirty()
|
||||
|
||||
def title_changed(self, window):
|
||||
if window is self.active_window:
|
||||
tm = self.tab_manager_ref()
|
||||
if tm is not None:
|
||||
tm.title_changed(window.title)
|
||||
tm.mark_tab_bar_dirty()
|
||||
|
||||
def on_bell(self, window):
|
||||
tm = self.tab_manager_ref()
|
||||
if tm is not None:
|
||||
self.relayout_borders()
|
||||
tm.mark_tab_bar_dirty()
|
||||
|
||||
def visible_windows(self):
|
||||
for w in self.windows:
|
||||
@@ -100,17 +133,22 @@ class Tab: # {{{
|
||||
tm = self.tab_manager_ref()
|
||||
if tm is not None:
|
||||
visible_windows = [w for w in self.windows if w.is_visible_in_layout]
|
||||
self.borders(visible_windows, self.active_window, self.current_layout,
|
||||
w = self.active_window
|
||||
self.borders(visible_windows, w, self.current_layout,
|
||||
tm.blank_rects, self.current_layout.needs_window_borders and len(visible_windows) > 1)
|
||||
if w is not None:
|
||||
w.change_titlebar_color()
|
||||
|
||||
def create_layout_object(self, idx):
|
||||
return all_layouts[idx](self.os_window_id, self.id, self.opts, self.borders.border_width)
|
||||
def create_layout_object(self, name):
|
||||
return create_layout_object_for(name, self.os_window_id, self.id, self.margin_width, self.padding_width, self.borders.border_width)
|
||||
|
||||
def next_layout(self):
|
||||
if len(self.enabled_layouts) > 1:
|
||||
try:
|
||||
idx = self.enabled_layouts.index(self.current_layout.name)
|
||||
except Exception:
|
||||
for i, layout_name in enumerate(self.enabled_layouts):
|
||||
if layout_name == self.current_layout.full_name:
|
||||
idx = i
|
||||
break
|
||||
else:
|
||||
idx = -1
|
||||
nl = self.enabled_layouts[(idx + 1) % len(self.enabled_layouts)]
|
||||
self.current_layout = self.create_layout_object(nl)
|
||||
@@ -124,6 +162,17 @@ class Tab: # {{{
|
||||
self.current_layout = self.create_layout_object(layout_name)
|
||||
self.relayout()
|
||||
|
||||
def resize_window_by(self, window_id, increment, is_horizontal):
|
||||
increment_as_percent = self.current_layout.bias_increment_for_cell(is_horizontal) * increment
|
||||
if self.current_layout.modify_size_of_window(self.windows, window_id, increment_as_percent, is_horizontal):
|
||||
self.relayout()
|
||||
return
|
||||
return 'Could not resize'
|
||||
|
||||
def reset_window_sizes(self):
|
||||
if self.current_layout.remove_all_biases():
|
||||
self.relayout()
|
||||
|
||||
def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None, env=None):
|
||||
if cmd is None:
|
||||
if use_shell:
|
||||
@@ -133,6 +182,7 @@ class Tab: # {{{
|
||||
fenv = {}
|
||||
if env:
|
||||
fenv.update(env)
|
||||
fenv['KITTY_WINDOW_ID'] = str(next_window_id())
|
||||
if not is_macos and not is_wayland:
|
||||
try:
|
||||
fenv['WINDOWID'] = str(x11_window_id(self.os_window_id))
|
||||
@@ -183,6 +233,10 @@ class Tab: # {{{
|
||||
return
|
||||
self.set_active_window_idx(idx)
|
||||
|
||||
def get_nth_window(self, n):
|
||||
if self.windows:
|
||||
return self.current_layout.nth_window(self.windows, n, make_active=False)
|
||||
|
||||
def nth_window(self, num=0):
|
||||
if self.windows:
|
||||
self.active_window_idx = self.current_layout.nth_window(self.windows, num)
|
||||
@@ -227,7 +281,7 @@ class Tab: # {{{
|
||||
return False
|
||||
|
||||
def __iter__(self):
|
||||
yield from iter(self.windows)
|
||||
return iter(self.windows)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.windows)
|
||||
@@ -236,6 +290,7 @@ class Tab: # {{{
|
||||
return window in self.windows
|
||||
|
||||
def destroy(self):
|
||||
evict_cached_layouts(self.id)
|
||||
for w in self.windows:
|
||||
w.destroy()
|
||||
self.windows = deque()
|
||||
@@ -279,6 +334,7 @@ class TabBar: # {{{
|
||||
|
||||
self.active_bg = as_rgb(color_as_int(opts.active_tab_background))
|
||||
self.active_fg = as_rgb(color_as_int(opts.active_tab_foreground))
|
||||
self.bell_fg = as_rgb(0xff0000)
|
||||
|
||||
def patch_colors(self, spec):
|
||||
if 'active_tab_foreground' in spec:
|
||||
@@ -322,10 +378,18 @@ class TabBar: # {{{
|
||||
|
||||
for t in data:
|
||||
s.cursor.bg = self.active_bg if t.is_active else 0
|
||||
s.cursor.fg = self.active_fg if t.is_active else 0
|
||||
s.cursor.fg = fg = self.active_fg if t.is_active else 0
|
||||
s.cursor.bold, s.cursor.italic = self.active_font_style if t.is_active else self.inactive_font_style
|
||||
before = s.cursor.x
|
||||
s.draw(' ' * self.leading_spaces + t.title + ' ' * self.trailing_spaces)
|
||||
if self.leading_spaces:
|
||||
s.draw(' ' * self.leading_spaces)
|
||||
if t.needs_attention and self.opts.bell_on_tab:
|
||||
s.cursor.fg = self.bell_fg
|
||||
s.draw('🔔 ')
|
||||
s.cursor.fg = fg
|
||||
s.draw(t.title)
|
||||
if self.trailing_spaces:
|
||||
s.draw(' ' * self.trailing_spaces)
|
||||
extra = s.cursor.x - before - max_title_length
|
||||
if extra > 0:
|
||||
s.cursor.x -= extra + 1
|
||||
@@ -360,12 +424,36 @@ class TabManager: # {{{
|
||||
self.opts, self.args = opts, args
|
||||
self.tabs = []
|
||||
self.tab_bar = TabBar(self.os_window_id, opts)
|
||||
self.active_tab_idx = 0
|
||||
self._active_tab_idx = 0
|
||||
|
||||
for t in startup_session.tabs:
|
||||
self._add_tab(Tab(self, session_tab=t))
|
||||
self._set_active_tab(max(0, min(startup_session.active_tab_idx, len(self.tabs) - 1)))
|
||||
self.update_tab_bar()
|
||||
|
||||
@property
|
||||
def active_tab_idx(self):
|
||||
return self._active_tab_idx
|
||||
|
||||
@active_tab_idx.setter
|
||||
def active_tab_idx(self, val):
|
||||
try:
|
||||
old_active_tab = self.tabs[self._active_tab_idx]
|
||||
except Exception:
|
||||
old_active_tab = None
|
||||
self._active_tab_idx = max(0, min(val, len(self.tabs) - 1))
|
||||
try:
|
||||
new_active_tab = self.tabs[self._active_tab_idx]
|
||||
except Exception:
|
||||
new_active_tab = None
|
||||
if old_active_tab is not new_active_tab:
|
||||
if old_active_tab is not None:
|
||||
w = old_active_tab.active_window
|
||||
if w is not None:
|
||||
w.focus_changed(False)
|
||||
if new_active_tab is not None:
|
||||
w = new_active_tab.active_window
|
||||
if w is not None:
|
||||
w.focus_changed(True)
|
||||
|
||||
def refresh_sprite_positions(self):
|
||||
self.tab_bar.screen.refresh_sprite_positions()
|
||||
@@ -392,21 +480,24 @@ class TabManager: # {{{
|
||||
self.resize(only_tabs=True)
|
||||
glfw_post_empty_event()
|
||||
|
||||
def update_tab_bar(self):
|
||||
def mark_tab_bar_dirty(self):
|
||||
if len(self.tabs) > 1:
|
||||
self.tab_bar.update(self.tab_bar_data)
|
||||
mark_tab_bar_dirty(self.os_window_id)
|
||||
|
||||
def update_tab_bar_data(self):
|
||||
self.tab_bar.update(self.tab_bar_data)
|
||||
|
||||
def resize(self, only_tabs=False):
|
||||
if not only_tabs:
|
||||
self.tab_bar.layout()
|
||||
self.update_tab_bar()
|
||||
self.mark_tab_bar_dirty()
|
||||
for tab in self.tabs:
|
||||
tab.relayout()
|
||||
|
||||
def set_active_tab_idx(self, idx):
|
||||
self._set_active_tab(idx)
|
||||
self.active_tab.relayout_borders()
|
||||
self.update_tab_bar()
|
||||
self.mark_tab_bar_dirty()
|
||||
|
||||
def set_active_tab(self, tab):
|
||||
try:
|
||||
@@ -459,22 +550,19 @@ class TabManager: # {{{
|
||||
self.tabs[idx], self.tabs[nidx] = self.tabs[nidx], self.tabs[idx]
|
||||
swap_tabs(self.os_window_id, idx, nidx)
|
||||
self._set_active_tab(nidx)
|
||||
self.update_tab_bar()
|
||||
|
||||
def title_changed(self, new_title):
|
||||
self.update_tab_bar()
|
||||
self.mark_tab_bar_dirty()
|
||||
|
||||
def new_tab(self, special_window=None, cwd_from=None):
|
||||
idx = len(self.tabs)
|
||||
self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from))
|
||||
self._set_active_tab(idx)
|
||||
self.update_tab_bar()
|
||||
self.mark_tab_bar_dirty()
|
||||
return self.tabs[idx]
|
||||
|
||||
def remove(self, tab):
|
||||
self._remove_tab(tab)
|
||||
self._set_active_tab(max(0, min(self.active_tab_idx, len(self.tabs) - 1)))
|
||||
self.update_tab_bar()
|
||||
self.mark_tab_bar_dirty()
|
||||
tab.destroy()
|
||||
|
||||
@property
|
||||
@@ -483,7 +571,12 @@ class TabManager: # {{{
|
||||
ans = []
|
||||
for t in self.tabs:
|
||||
title = (t.name or t.title or appname).strip()
|
||||
ans.append(TabbarData(title, t is at, t is self.tabs[-1]))
|
||||
needs_attention = False
|
||||
for w in t:
|
||||
if w.needs_attention:
|
||||
needs_attention = True
|
||||
break
|
||||
ans.append(TabbarData(title, t is at, t is self.tabs[-1], needs_attention))
|
||||
return ans
|
||||
|
||||
def activate_tab_at(self, x):
|
||||
|
||||
@@ -173,6 +173,19 @@ string_capabilities = {
|
||||
'kf10': r'\E[21~',
|
||||
'kf11': r'\E[23~',
|
||||
'kf12': r'\E[24~',
|
||||
'kf13': r'\E[1;2P',
|
||||
'kf14': r'\E[1;2Q',
|
||||
'kf15': r'\E[1;2R',
|
||||
'kf16': r'\E[1;2S',
|
||||
'kf17': r'\E[15;2~',
|
||||
'kf18': r'\E[17;2~',
|
||||
'kf19': r'\E[18;2~',
|
||||
'kf20': r'\E[19;2~',
|
||||
'kf21': r'\E[20;2~',
|
||||
'kf22': r'\E[21;2~',
|
||||
'kf23': r'\E[23;2~',
|
||||
'kf24': r'\E[24;2~',
|
||||
'kf25': r'\E[1;5P',
|
||||
# Home
|
||||
'khome': r'\EOH',
|
||||
# End
|
||||
@@ -345,6 +358,19 @@ termcap_aliases.update({
|
||||
'k;': 'kf10',
|
||||
'F1': 'kf11',
|
||||
'F2': 'kf12',
|
||||
'F3': 'kf13',
|
||||
'F4': 'kf14',
|
||||
'F5': 'kf15',
|
||||
'F6': 'kf16',
|
||||
'F7': 'kf17',
|
||||
'F8': 'kf18',
|
||||
'F9': 'kf19',
|
||||
'FA': 'kf20',
|
||||
'FB': 'kf21',
|
||||
'FC': 'kf22',
|
||||
'FD': 'kf23',
|
||||
'FE': 'kf24',
|
||||
'FF': 'kf25',
|
||||
'kh': 'khome',
|
||||
'@7': 'kend',
|
||||
'kI': 'kich1',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// unicode data, built from the unicode standard on: 2018-02-09
|
||||
// unicode data, built from the unicode standard on: 2018-05-01
|
||||
// see gen-wcwidth.py
|
||||
#include "data-types.h"
|
||||
|
||||
@@ -2335,269 +2335,269 @@ char_type codepoint_for_mark(combining_type m) {
|
||||
combining_type mark_for_codepoint(char_type c) {
|
||||
switch(c) { // {{{
|
||||
case 0: return 0;
|
||||
case 768 ... 879: return 1 + c - 768;
|
||||
case 1155 ... 1161: return 113 + c - 1155;
|
||||
case 1425 ... 1469: return 120 + c - 1425;
|
||||
case 768: case 769: case 770: case 771: case 772: case 773: case 774: case 775: case 776: case 777: case 778: case 779: case 780: case 781: case 782: case 783: case 784: case 785: case 786: case 787: case 788: case 789: case 790: case 791: case 792: case 793: case 794: case 795: case 796: case 797: case 798: case 799: case 800: case 801: case 802: case 803: case 804: case 805: case 806: case 807: case 808: case 809: case 810: case 811: case 812: case 813: case 814: case 815: case 816: case 817: case 818: case 819: case 820: case 821: case 822: case 823: case 824: case 825: case 826: case 827: case 828: case 829: case 830: case 831: case 832: case 833: case 834: case 835: case 836: case 837: case 838: case 839: case 840: case 841: case 842: case 843: case 844: case 845: case 846: case 847: case 848: case 849: case 850: case 851: case 852: case 853: case 854: case 855: case 856: case 857: case 858: case 859: case 860: case 861: case 862: case 863: case 864: case 865: case 866: case 867: case 868: case 869: case 870: case 871: case 872: case 873: case 874: case 875: case 876: case 877: case 878: case 879: return 1 + c - 768;
|
||||
case 1155: case 1156: case 1157: case 1158: case 1159: case 1160: case 1161: return 113 + c - 1155;
|
||||
case 1425: case 1426: case 1427: case 1428: case 1429: case 1430: case 1431: case 1432: case 1433: case 1434: case 1435: case 1436: case 1437: case 1438: case 1439: case 1440: case 1441: case 1442: case 1443: case 1444: case 1445: case 1446: case 1447: case 1448: case 1449: case 1450: case 1451: case 1452: case 1453: case 1454: case 1455: case 1456: case 1457: case 1458: case 1459: case 1460: case 1461: case 1462: case 1463: case 1464: case 1465: case 1466: case 1467: case 1468: case 1469: return 120 + c - 1425;
|
||||
case 1471: return 165;
|
||||
case 1473 ... 1474: return 166 + c - 1473;
|
||||
case 1476 ... 1477: return 168 + c - 1476;
|
||||
case 1473: case 1474: return 166 + c - 1473;
|
||||
case 1476: case 1477: return 168 + c - 1476;
|
||||
case 1479: return 170;
|
||||
case 1552 ... 1562: return 171 + c - 1552;
|
||||
case 1611 ... 1631: return 182 + c - 1611;
|
||||
case 1552: case 1553: case 1554: case 1555: case 1556: case 1557: case 1558: case 1559: case 1560: case 1561: case 1562: return 171 + c - 1552;
|
||||
case 1611: case 1612: case 1613: case 1614: case 1615: case 1616: case 1617: case 1618: case 1619: case 1620: case 1621: case 1622: case 1623: case 1624: case 1625: case 1626: case 1627: case 1628: case 1629: case 1630: case 1631: return 182 + c - 1611;
|
||||
case 1648: return 203;
|
||||
case 1750 ... 1756: return 204 + c - 1750;
|
||||
case 1759 ... 1764: return 211 + c - 1759;
|
||||
case 1767 ... 1768: return 217 + c - 1767;
|
||||
case 1770 ... 1773: return 219 + c - 1770;
|
||||
case 1750: case 1751: case 1752: case 1753: case 1754: case 1755: case 1756: return 204 + c - 1750;
|
||||
case 1759: case 1760: case 1761: case 1762: case 1763: case 1764: return 211 + c - 1759;
|
||||
case 1767: case 1768: return 217 + c - 1767;
|
||||
case 1770: case 1771: case 1772: case 1773: return 219 + c - 1770;
|
||||
case 1809: return 223;
|
||||
case 1840 ... 1866: return 224 + c - 1840;
|
||||
case 1958 ... 1968: return 251 + c - 1958;
|
||||
case 2027 ... 2035: return 262 + c - 2027;
|
||||
case 2070 ... 2073: return 271 + c - 2070;
|
||||
case 2075 ... 2083: return 275 + c - 2075;
|
||||
case 2085 ... 2087: return 284 + c - 2085;
|
||||
case 2089 ... 2093: return 287 + c - 2089;
|
||||
case 2137 ... 2139: return 292 + c - 2137;
|
||||
case 2260 ... 2273: return 295 + c - 2260;
|
||||
case 2275 ... 2307: return 309 + c - 2275;
|
||||
case 2362 ... 2364: return 342 + c - 2362;
|
||||
case 2366 ... 2383: return 345 + c - 2366;
|
||||
case 2385 ... 2391: return 363 + c - 2385;
|
||||
case 2402 ... 2403: return 370 + c - 2402;
|
||||
case 2433 ... 2435: return 372 + c - 2433;
|
||||
case 1840: case 1841: case 1842: case 1843: case 1844: case 1845: case 1846: case 1847: case 1848: case 1849: case 1850: case 1851: case 1852: case 1853: case 1854: case 1855: case 1856: case 1857: case 1858: case 1859: case 1860: case 1861: case 1862: case 1863: case 1864: case 1865: case 1866: return 224 + c - 1840;
|
||||
case 1958: case 1959: case 1960: case 1961: case 1962: case 1963: case 1964: case 1965: case 1966: case 1967: case 1968: return 251 + c - 1958;
|
||||
case 2027: case 2028: case 2029: case 2030: case 2031: case 2032: case 2033: case 2034: case 2035: return 262 + c - 2027;
|
||||
case 2070: case 2071: case 2072: case 2073: return 271 + c - 2070;
|
||||
case 2075: case 2076: case 2077: case 2078: case 2079: case 2080: case 2081: case 2082: case 2083: return 275 + c - 2075;
|
||||
case 2085: case 2086: case 2087: return 284 + c - 2085;
|
||||
case 2089: case 2090: case 2091: case 2092: case 2093: return 287 + c - 2089;
|
||||
case 2137: case 2138: case 2139: return 292 + c - 2137;
|
||||
case 2260: case 2261: case 2262: case 2263: case 2264: case 2265: case 2266: case 2267: case 2268: case 2269: case 2270: case 2271: case 2272: case 2273: return 295 + c - 2260;
|
||||
case 2275: case 2276: case 2277: case 2278: case 2279: case 2280: case 2281: case 2282: case 2283: case 2284: case 2285: case 2286: case 2287: case 2288: case 2289: case 2290: case 2291: case 2292: case 2293: case 2294: case 2295: case 2296: case 2297: case 2298: case 2299: case 2300: case 2301: case 2302: case 2303: case 2304: case 2305: case 2306: case 2307: return 309 + c - 2275;
|
||||
case 2362: case 2363: case 2364: return 342 + c - 2362;
|
||||
case 2366: case 2367: case 2368: case 2369: case 2370: case 2371: case 2372: case 2373: case 2374: case 2375: case 2376: case 2377: case 2378: case 2379: case 2380: case 2381: case 2382: case 2383: return 345 + c - 2366;
|
||||
case 2385: case 2386: case 2387: case 2388: case 2389: case 2390: case 2391: return 363 + c - 2385;
|
||||
case 2402: case 2403: return 370 + c - 2402;
|
||||
case 2433: case 2434: case 2435: return 372 + c - 2433;
|
||||
case 2492: return 375;
|
||||
case 2494 ... 2500: return 376 + c - 2494;
|
||||
case 2503 ... 2504: return 383 + c - 2503;
|
||||
case 2507 ... 2509: return 385 + c - 2507;
|
||||
case 2494: case 2495: case 2496: case 2497: case 2498: case 2499: case 2500: return 376 + c - 2494;
|
||||
case 2503: case 2504: return 383 + c - 2503;
|
||||
case 2507: case 2508: case 2509: return 385 + c - 2507;
|
||||
case 2519: return 388;
|
||||
case 2530 ... 2531: return 389 + c - 2530;
|
||||
case 2561 ... 2563: return 391 + c - 2561;
|
||||
case 2530: case 2531: return 389 + c - 2530;
|
||||
case 2561: case 2562: case 2563: return 391 + c - 2561;
|
||||
case 2620: return 394;
|
||||
case 2622 ... 2626: return 395 + c - 2622;
|
||||
case 2631 ... 2632: return 400 + c - 2631;
|
||||
case 2635 ... 2637: return 402 + c - 2635;
|
||||
case 2622: case 2623: case 2624: case 2625: case 2626: return 395 + c - 2622;
|
||||
case 2631: case 2632: return 400 + c - 2631;
|
||||
case 2635: case 2636: case 2637: return 402 + c - 2635;
|
||||
case 2641: return 405;
|
||||
case 2672 ... 2673: return 406 + c - 2672;
|
||||
case 2672: case 2673: return 406 + c - 2672;
|
||||
case 2677: return 408;
|
||||
case 2689 ... 2691: return 409 + c - 2689;
|
||||
case 2689: case 2690: case 2691: return 409 + c - 2689;
|
||||
case 2748: return 412;
|
||||
case 2750 ... 2757: return 413 + c - 2750;
|
||||
case 2759 ... 2761: return 421 + c - 2759;
|
||||
case 2763 ... 2765: return 424 + c - 2763;
|
||||
case 2786 ... 2787: return 427 + c - 2786;
|
||||
case 2810 ... 2815: return 429 + c - 2810;
|
||||
case 2817 ... 2819: return 435 + c - 2817;
|
||||
case 2750: case 2751: case 2752: case 2753: case 2754: case 2755: case 2756: case 2757: return 413 + c - 2750;
|
||||
case 2759: case 2760: case 2761: return 421 + c - 2759;
|
||||
case 2763: case 2764: case 2765: return 424 + c - 2763;
|
||||
case 2786: case 2787: return 427 + c - 2786;
|
||||
case 2810: case 2811: case 2812: case 2813: case 2814: case 2815: return 429 + c - 2810;
|
||||
case 2817: case 2818: case 2819: return 435 + c - 2817;
|
||||
case 2876: return 438;
|
||||
case 2878 ... 2884: return 439 + c - 2878;
|
||||
case 2887 ... 2888: return 446 + c - 2887;
|
||||
case 2891 ... 2893: return 448 + c - 2891;
|
||||
case 2902 ... 2903: return 451 + c - 2902;
|
||||
case 2914 ... 2915: return 453 + c - 2914;
|
||||
case 2878: case 2879: case 2880: case 2881: case 2882: case 2883: case 2884: return 439 + c - 2878;
|
||||
case 2887: case 2888: return 446 + c - 2887;
|
||||
case 2891: case 2892: case 2893: return 448 + c - 2891;
|
||||
case 2902: case 2903: return 451 + c - 2902;
|
||||
case 2914: case 2915: return 453 + c - 2914;
|
||||
case 2946: return 455;
|
||||
case 3006 ... 3010: return 456 + c - 3006;
|
||||
case 3014 ... 3016: return 461 + c - 3014;
|
||||
case 3018 ... 3021: return 464 + c - 3018;
|
||||
case 3006: case 3007: case 3008: case 3009: case 3010: return 456 + c - 3006;
|
||||
case 3014: case 3015: case 3016: return 461 + c - 3014;
|
||||
case 3018: case 3019: case 3020: case 3021: return 464 + c - 3018;
|
||||
case 3031: return 468;
|
||||
case 3072 ... 3075: return 469 + c - 3072;
|
||||
case 3134 ... 3140: return 473 + c - 3134;
|
||||
case 3142 ... 3144: return 480 + c - 3142;
|
||||
case 3146 ... 3149: return 483 + c - 3146;
|
||||
case 3157 ... 3158: return 487 + c - 3157;
|
||||
case 3170 ... 3171: return 489 + c - 3170;
|
||||
case 3201 ... 3203: return 491 + c - 3201;
|
||||
case 3072: case 3073: case 3074: case 3075: return 469 + c - 3072;
|
||||
case 3134: case 3135: case 3136: case 3137: case 3138: case 3139: case 3140: return 473 + c - 3134;
|
||||
case 3142: case 3143: case 3144: return 480 + c - 3142;
|
||||
case 3146: case 3147: case 3148: case 3149: return 483 + c - 3146;
|
||||
case 3157: case 3158: return 487 + c - 3157;
|
||||
case 3170: case 3171: return 489 + c - 3170;
|
||||
case 3201: case 3202: case 3203: return 491 + c - 3201;
|
||||
case 3260: return 494;
|
||||
case 3262 ... 3268: return 495 + c - 3262;
|
||||
case 3270 ... 3272: return 502 + c - 3270;
|
||||
case 3274 ... 3277: return 505 + c - 3274;
|
||||
case 3285 ... 3286: return 509 + c - 3285;
|
||||
case 3298 ... 3299: return 511 + c - 3298;
|
||||
case 3328 ... 3331: return 513 + c - 3328;
|
||||
case 3387 ... 3388: return 517 + c - 3387;
|
||||
case 3390 ... 3396: return 519 + c - 3390;
|
||||
case 3398 ... 3400: return 526 + c - 3398;
|
||||
case 3402 ... 3405: return 529 + c - 3402;
|
||||
case 3262: case 3263: case 3264: case 3265: case 3266: case 3267: case 3268: return 495 + c - 3262;
|
||||
case 3270: case 3271: case 3272: return 502 + c - 3270;
|
||||
case 3274: case 3275: case 3276: case 3277: return 505 + c - 3274;
|
||||
case 3285: case 3286: return 509 + c - 3285;
|
||||
case 3298: case 3299: return 511 + c - 3298;
|
||||
case 3328: case 3329: case 3330: case 3331: return 513 + c - 3328;
|
||||
case 3387: case 3388: return 517 + c - 3387;
|
||||
case 3390: case 3391: case 3392: case 3393: case 3394: case 3395: case 3396: return 519 + c - 3390;
|
||||
case 3398: case 3399: case 3400: return 526 + c - 3398;
|
||||
case 3402: case 3403: case 3404: case 3405: return 529 + c - 3402;
|
||||
case 3415: return 533;
|
||||
case 3426 ... 3427: return 534 + c - 3426;
|
||||
case 3458 ... 3459: return 536 + c - 3458;
|
||||
case 3426: case 3427: return 534 + c - 3426;
|
||||
case 3458: case 3459: return 536 + c - 3458;
|
||||
case 3530: return 538;
|
||||
case 3535 ... 3540: return 539 + c - 3535;
|
||||
case 3535: case 3536: case 3537: case 3538: case 3539: case 3540: return 539 + c - 3535;
|
||||
case 3542: return 545;
|
||||
case 3544 ... 3551: return 546 + c - 3544;
|
||||
case 3570 ... 3571: return 554 + c - 3570;
|
||||
case 3544: case 3545: case 3546: case 3547: case 3548: case 3549: case 3550: case 3551: return 546 + c - 3544;
|
||||
case 3570: case 3571: return 554 + c - 3570;
|
||||
case 3633: return 556;
|
||||
case 3636 ... 3642: return 557 + c - 3636;
|
||||
case 3655 ... 3662: return 564 + c - 3655;
|
||||
case 3636: case 3637: case 3638: case 3639: case 3640: case 3641: case 3642: return 557 + c - 3636;
|
||||
case 3655: case 3656: case 3657: case 3658: case 3659: case 3660: case 3661: case 3662: return 564 + c - 3655;
|
||||
case 3761: return 572;
|
||||
case 3764 ... 3769: return 573 + c - 3764;
|
||||
case 3771 ... 3772: return 579 + c - 3771;
|
||||
case 3784 ... 3789: return 581 + c - 3784;
|
||||
case 3864 ... 3865: return 587 + c - 3864;
|
||||
case 3764: case 3765: case 3766: case 3767: case 3768: case 3769: return 573 + c - 3764;
|
||||
case 3771: case 3772: return 579 + c - 3771;
|
||||
case 3784: case 3785: case 3786: case 3787: case 3788: case 3789: return 581 + c - 3784;
|
||||
case 3864: case 3865: return 587 + c - 3864;
|
||||
case 3893: return 589;
|
||||
case 3895: return 590;
|
||||
case 3897: return 591;
|
||||
case 3902 ... 3903: return 592 + c - 3902;
|
||||
case 3953 ... 3972: return 594 + c - 3953;
|
||||
case 3974 ... 3975: return 614 + c - 3974;
|
||||
case 3981 ... 3991: return 616 + c - 3981;
|
||||
case 3993 ... 4028: return 627 + c - 3993;
|
||||
case 3902: case 3903: return 592 + c - 3902;
|
||||
case 3953: case 3954: case 3955: case 3956: case 3957: case 3958: case 3959: case 3960: case 3961: case 3962: case 3963: case 3964: case 3965: case 3966: case 3967: case 3968: case 3969: case 3970: case 3971: case 3972: return 594 + c - 3953;
|
||||
case 3974: case 3975: return 614 + c - 3974;
|
||||
case 3981: case 3982: case 3983: case 3984: case 3985: case 3986: case 3987: case 3988: case 3989: case 3990: case 3991: return 616 + c - 3981;
|
||||
case 3993: case 3994: case 3995: case 3996: case 3997: case 3998: case 3999: case 4000: case 4001: case 4002: case 4003: case 4004: case 4005: case 4006: case 4007: case 4008: case 4009: case 4010: case 4011: case 4012: case 4013: case 4014: case 4015: case 4016: case 4017: case 4018: case 4019: case 4020: case 4021: case 4022: case 4023: case 4024: case 4025: case 4026: case 4027: case 4028: return 627 + c - 3993;
|
||||
case 4038: return 663;
|
||||
case 4139 ... 4158: return 664 + c - 4139;
|
||||
case 4182 ... 4185: return 684 + c - 4182;
|
||||
case 4190 ... 4192: return 688 + c - 4190;
|
||||
case 4194 ... 4196: return 691 + c - 4194;
|
||||
case 4199 ... 4205: return 694 + c - 4199;
|
||||
case 4209 ... 4212: return 701 + c - 4209;
|
||||
case 4226 ... 4237: return 705 + c - 4226;
|
||||
case 4139: case 4140: case 4141: case 4142: case 4143: case 4144: case 4145: case 4146: case 4147: case 4148: case 4149: case 4150: case 4151: case 4152: case 4153: case 4154: case 4155: case 4156: case 4157: case 4158: return 664 + c - 4139;
|
||||
case 4182: case 4183: case 4184: case 4185: return 684 + c - 4182;
|
||||
case 4190: case 4191: case 4192: return 688 + c - 4190;
|
||||
case 4194: case 4195: case 4196: return 691 + c - 4194;
|
||||
case 4199: case 4200: case 4201: case 4202: case 4203: case 4204: case 4205: return 694 + c - 4199;
|
||||
case 4209: case 4210: case 4211: case 4212: return 701 + c - 4209;
|
||||
case 4226: case 4227: case 4228: case 4229: case 4230: case 4231: case 4232: case 4233: case 4234: case 4235: case 4236: case 4237: return 705 + c - 4226;
|
||||
case 4239: return 717;
|
||||
case 4250 ... 4253: return 718 + c - 4250;
|
||||
case 4957 ... 4959: return 722 + c - 4957;
|
||||
case 5906 ... 5908: return 725 + c - 5906;
|
||||
case 5938 ... 5940: return 728 + c - 5938;
|
||||
case 5970 ... 5971: return 731 + c - 5970;
|
||||
case 6002 ... 6003: return 733 + c - 6002;
|
||||
case 6068 ... 6099: return 735 + c - 6068;
|
||||
case 4250: case 4251: case 4252: case 4253: return 718 + c - 4250;
|
||||
case 4957: case 4958: case 4959: return 722 + c - 4957;
|
||||
case 5906: case 5907: case 5908: return 725 + c - 5906;
|
||||
case 5938: case 5939: case 5940: return 728 + c - 5938;
|
||||
case 5970: case 5971: return 731 + c - 5970;
|
||||
case 6002: case 6003: return 733 + c - 6002;
|
||||
case 6068: case 6069: case 6070: case 6071: case 6072: case 6073: case 6074: case 6075: case 6076: case 6077: case 6078: case 6079: case 6080: case 6081: case 6082: case 6083: case 6084: case 6085: case 6086: case 6087: case 6088: case 6089: case 6090: case 6091: case 6092: case 6093: case 6094: case 6095: case 6096: case 6097: case 6098: case 6099: return 735 + c - 6068;
|
||||
case 6109: return 767;
|
||||
case 6155 ... 6157: return 768 + c - 6155;
|
||||
case 6277 ... 6278: return 771 + c - 6277;
|
||||
case 6155: case 6156: case 6157: return 768 + c - 6155;
|
||||
case 6277: case 6278: return 771 + c - 6277;
|
||||
case 6313: return 773;
|
||||
case 6432 ... 6443: return 774 + c - 6432;
|
||||
case 6448 ... 6459: return 786 + c - 6448;
|
||||
case 6679 ... 6683: return 798 + c - 6679;
|
||||
case 6741 ... 6750: return 803 + c - 6741;
|
||||
case 6752 ... 6780: return 813 + c - 6752;
|
||||
case 6432: case 6433: case 6434: case 6435: case 6436: case 6437: case 6438: case 6439: case 6440: case 6441: case 6442: case 6443: return 774 + c - 6432;
|
||||
case 6448: case 6449: case 6450: case 6451: case 6452: case 6453: case 6454: case 6455: case 6456: case 6457: case 6458: case 6459: return 786 + c - 6448;
|
||||
case 6679: case 6680: case 6681: case 6682: case 6683: return 798 + c - 6679;
|
||||
case 6741: case 6742: case 6743: case 6744: case 6745: case 6746: case 6747: case 6748: case 6749: case 6750: return 803 + c - 6741;
|
||||
case 6752: case 6753: case 6754: case 6755: case 6756: case 6757: case 6758: case 6759: case 6760: case 6761: case 6762: case 6763: case 6764: case 6765: case 6766: case 6767: case 6768: case 6769: case 6770: case 6771: case 6772: case 6773: case 6774: case 6775: case 6776: case 6777: case 6778: case 6779: case 6780: return 813 + c - 6752;
|
||||
case 6783: return 842;
|
||||
case 6832 ... 6846: return 843 + c - 6832;
|
||||
case 6912 ... 6916: return 858 + c - 6912;
|
||||
case 6964 ... 6980: return 863 + c - 6964;
|
||||
case 7019 ... 7027: return 880 + c - 7019;
|
||||
case 7040 ... 7042: return 889 + c - 7040;
|
||||
case 7073 ... 7085: return 892 + c - 7073;
|
||||
case 7142 ... 7155: return 905 + c - 7142;
|
||||
case 7204 ... 7223: return 919 + c - 7204;
|
||||
case 7376 ... 7378: return 939 + c - 7376;
|
||||
case 7380 ... 7400: return 942 + c - 7380;
|
||||
case 6832: case 6833: case 6834: case 6835: case 6836: case 6837: case 6838: case 6839: case 6840: case 6841: case 6842: case 6843: case 6844: case 6845: case 6846: return 843 + c - 6832;
|
||||
case 6912: case 6913: case 6914: case 6915: case 6916: return 858 + c - 6912;
|
||||
case 6964: case 6965: case 6966: case 6967: case 6968: case 6969: case 6970: case 6971: case 6972: case 6973: case 6974: case 6975: case 6976: case 6977: case 6978: case 6979: case 6980: return 863 + c - 6964;
|
||||
case 7019: case 7020: case 7021: case 7022: case 7023: case 7024: case 7025: case 7026: case 7027: return 880 + c - 7019;
|
||||
case 7040: case 7041: case 7042: return 889 + c - 7040;
|
||||
case 7073: case 7074: case 7075: case 7076: case 7077: case 7078: case 7079: case 7080: case 7081: case 7082: case 7083: case 7084: case 7085: return 892 + c - 7073;
|
||||
case 7142: case 7143: case 7144: case 7145: case 7146: case 7147: case 7148: case 7149: case 7150: case 7151: case 7152: case 7153: case 7154: case 7155: return 905 + c - 7142;
|
||||
case 7204: case 7205: case 7206: case 7207: case 7208: case 7209: case 7210: case 7211: case 7212: case 7213: case 7214: case 7215: case 7216: case 7217: case 7218: case 7219: case 7220: case 7221: case 7222: case 7223: return 919 + c - 7204;
|
||||
case 7376: case 7377: case 7378: return 939 + c - 7376;
|
||||
case 7380: case 7381: case 7382: case 7383: case 7384: case 7385: case 7386: case 7387: case 7388: case 7389: case 7390: case 7391: case 7392: case 7393: case 7394: case 7395: case 7396: case 7397: case 7398: case 7399: case 7400: return 942 + c - 7380;
|
||||
case 7405: return 963;
|
||||
case 7410 ... 7412: return 964 + c - 7410;
|
||||
case 7415 ... 7417: return 967 + c - 7415;
|
||||
case 7616 ... 7673: return 970 + c - 7616;
|
||||
case 7675 ... 7679: return 1028 + c - 7675;
|
||||
case 8400 ... 8432: return 1033 + c - 8400;
|
||||
case 11503 ... 11505: return 1066 + c - 11503;
|
||||
case 7410: case 7411: case 7412: return 964 + c - 7410;
|
||||
case 7415: case 7416: case 7417: return 967 + c - 7415;
|
||||
case 7616: case 7617: case 7618: case 7619: case 7620: case 7621: case 7622: case 7623: case 7624: case 7625: case 7626: case 7627: case 7628: case 7629: case 7630: case 7631: case 7632: case 7633: case 7634: case 7635: case 7636: case 7637: case 7638: case 7639: case 7640: case 7641: case 7642: case 7643: case 7644: case 7645: case 7646: case 7647: case 7648: case 7649: case 7650: case 7651: case 7652: case 7653: case 7654: case 7655: case 7656: case 7657: case 7658: case 7659: case 7660: case 7661: case 7662: case 7663: case 7664: case 7665: case 7666: case 7667: case 7668: case 7669: case 7670: case 7671: case 7672: case 7673: return 970 + c - 7616;
|
||||
case 7675: case 7676: case 7677: case 7678: case 7679: return 1028 + c - 7675;
|
||||
case 8400: case 8401: case 8402: case 8403: case 8404: case 8405: case 8406: case 8407: case 8408: case 8409: case 8410: case 8411: case 8412: case 8413: case 8414: case 8415: case 8416: case 8417: case 8418: case 8419: case 8420: case 8421: case 8422: case 8423: case 8424: case 8425: case 8426: case 8427: case 8428: case 8429: case 8430: case 8431: case 8432: return 1033 + c - 8400;
|
||||
case 11503: case 11504: case 11505: return 1066 + c - 11503;
|
||||
case 11647: return 1069;
|
||||
case 11744 ... 11775: return 1070 + c - 11744;
|
||||
case 12330 ... 12335: return 1102 + c - 12330;
|
||||
case 12441 ... 12442: return 1108 + c - 12441;
|
||||
case 42607 ... 42610: return 1110 + c - 42607;
|
||||
case 42612 ... 42621: return 1114 + c - 42612;
|
||||
case 42654 ... 42655: return 1124 + c - 42654;
|
||||
case 42736 ... 42737: return 1126 + c - 42736;
|
||||
case 11744: case 11745: case 11746: case 11747: case 11748: case 11749: case 11750: case 11751: case 11752: case 11753: case 11754: case 11755: case 11756: case 11757: case 11758: case 11759: case 11760: case 11761: case 11762: case 11763: case 11764: case 11765: case 11766: case 11767: case 11768: case 11769: case 11770: case 11771: case 11772: case 11773: case 11774: case 11775: return 1070 + c - 11744;
|
||||
case 12330: case 12331: case 12332: case 12333: case 12334: case 12335: return 1102 + c - 12330;
|
||||
case 12441: case 12442: return 1108 + c - 12441;
|
||||
case 42607: case 42608: case 42609: case 42610: return 1110 + c - 42607;
|
||||
case 42612: case 42613: case 42614: case 42615: case 42616: case 42617: case 42618: case 42619: case 42620: case 42621: return 1114 + c - 42612;
|
||||
case 42654: case 42655: return 1124 + c - 42654;
|
||||
case 42736: case 42737: return 1126 + c - 42736;
|
||||
case 43010: return 1128;
|
||||
case 43014: return 1129;
|
||||
case 43019: return 1130;
|
||||
case 43043 ... 43047: return 1131 + c - 43043;
|
||||
case 43136 ... 43137: return 1136 + c - 43136;
|
||||
case 43188 ... 43205: return 1138 + c - 43188;
|
||||
case 43232 ... 43249: return 1156 + c - 43232;
|
||||
case 43302 ... 43309: return 1174 + c - 43302;
|
||||
case 43335 ... 43347: return 1182 + c - 43335;
|
||||
case 43392 ... 43395: return 1195 + c - 43392;
|
||||
case 43443 ... 43456: return 1199 + c - 43443;
|
||||
case 43043: case 43044: case 43045: case 43046: case 43047: return 1131 + c - 43043;
|
||||
case 43136: case 43137: return 1136 + c - 43136;
|
||||
case 43188: case 43189: case 43190: case 43191: case 43192: case 43193: case 43194: case 43195: case 43196: case 43197: case 43198: case 43199: case 43200: case 43201: case 43202: case 43203: case 43204: case 43205: return 1138 + c - 43188;
|
||||
case 43232: case 43233: case 43234: case 43235: case 43236: case 43237: case 43238: case 43239: case 43240: case 43241: case 43242: case 43243: case 43244: case 43245: case 43246: case 43247: case 43248: case 43249: return 1156 + c - 43232;
|
||||
case 43302: case 43303: case 43304: case 43305: case 43306: case 43307: case 43308: case 43309: return 1174 + c - 43302;
|
||||
case 43335: case 43336: case 43337: case 43338: case 43339: case 43340: case 43341: case 43342: case 43343: case 43344: case 43345: case 43346: case 43347: return 1182 + c - 43335;
|
||||
case 43392: case 43393: case 43394: case 43395: return 1195 + c - 43392;
|
||||
case 43443: case 43444: case 43445: case 43446: case 43447: case 43448: case 43449: case 43450: case 43451: case 43452: case 43453: case 43454: case 43455: case 43456: return 1199 + c - 43443;
|
||||
case 43493: return 1213;
|
||||
case 43561 ... 43574: return 1214 + c - 43561;
|
||||
case 43561: case 43562: case 43563: case 43564: case 43565: case 43566: case 43567: case 43568: case 43569: case 43570: case 43571: case 43572: case 43573: case 43574: return 1214 + c - 43561;
|
||||
case 43587: return 1228;
|
||||
case 43596 ... 43597: return 1229 + c - 43596;
|
||||
case 43643 ... 43645: return 1231 + c - 43643;
|
||||
case 43596: case 43597: return 1229 + c - 43596;
|
||||
case 43643: case 43644: case 43645: return 1231 + c - 43643;
|
||||
case 43696: return 1234;
|
||||
case 43698 ... 43700: return 1235 + c - 43698;
|
||||
case 43703 ... 43704: return 1238 + c - 43703;
|
||||
case 43710 ... 43711: return 1240 + c - 43710;
|
||||
case 43698: case 43699: case 43700: return 1235 + c - 43698;
|
||||
case 43703: case 43704: return 1238 + c - 43703;
|
||||
case 43710: case 43711: return 1240 + c - 43710;
|
||||
case 43713: return 1242;
|
||||
case 43755 ... 43759: return 1243 + c - 43755;
|
||||
case 43765 ... 43766: return 1248 + c - 43765;
|
||||
case 44003 ... 44010: return 1250 + c - 44003;
|
||||
case 44012 ... 44013: return 1258 + c - 44012;
|
||||
case 43755: case 43756: case 43757: case 43758: case 43759: return 1243 + c - 43755;
|
||||
case 43765: case 43766: return 1248 + c - 43765;
|
||||
case 44003: case 44004: case 44005: case 44006: case 44007: case 44008: case 44009: case 44010: return 1250 + c - 44003;
|
||||
case 44012: case 44013: return 1258 + c - 44012;
|
||||
case 64286: return 1260;
|
||||
case 65024 ... 65039: return 1261 + c - 65024;
|
||||
case 65056 ... 65071: return 1277 + c - 65056;
|
||||
case 65024: case 65025: case 65026: case 65027: case 65028: case 65029: case 65030: case 65031: case 65032: case 65033: case 65034: case 65035: case 65036: case 65037: case 65038: case 65039: return 1261 + c - 65024;
|
||||
case 65056: case 65057: case 65058: case 65059: case 65060: case 65061: case 65062: case 65063: case 65064: case 65065: case 65066: case 65067: case 65068: case 65069: case 65070: case 65071: return 1277 + c - 65056;
|
||||
case 66045: return 1293;
|
||||
case 66272: return 1294;
|
||||
case 66422 ... 66426: return 1295 + c - 66422;
|
||||
case 68097 ... 68099: return 1300 + c - 68097;
|
||||
case 68101 ... 68102: return 1303 + c - 68101;
|
||||
case 68108 ... 68111: return 1305 + c - 68108;
|
||||
case 68152 ... 68154: return 1309 + c - 68152;
|
||||
case 66422: case 66423: case 66424: case 66425: case 66426: return 1295 + c - 66422;
|
||||
case 68097: case 68098: case 68099: return 1300 + c - 68097;
|
||||
case 68101: case 68102: return 1303 + c - 68101;
|
||||
case 68108: case 68109: case 68110: case 68111: return 1305 + c - 68108;
|
||||
case 68152: case 68153: case 68154: return 1309 + c - 68152;
|
||||
case 68159: return 1312;
|
||||
case 68325 ... 68326: return 1313 + c - 68325;
|
||||
case 69632 ... 69634: return 1315 + c - 69632;
|
||||
case 69688 ... 69702: return 1318 + c - 69688;
|
||||
case 69759 ... 69762: return 1333 + c - 69759;
|
||||
case 69808 ... 69818: return 1337 + c - 69808;
|
||||
case 69888 ... 69890: return 1348 + c - 69888;
|
||||
case 69927 ... 69940: return 1351 + c - 69927;
|
||||
case 68325: case 68326: return 1313 + c - 68325;
|
||||
case 69632: case 69633: case 69634: return 1315 + c - 69632;
|
||||
case 69688: case 69689: case 69690: case 69691: case 69692: case 69693: case 69694: case 69695: case 69696: case 69697: case 69698: case 69699: case 69700: case 69701: case 69702: return 1318 + c - 69688;
|
||||
case 69759: case 69760: case 69761: case 69762: return 1333 + c - 69759;
|
||||
case 69808: case 69809: case 69810: case 69811: case 69812: case 69813: case 69814: case 69815: case 69816: case 69817: case 69818: return 1337 + c - 69808;
|
||||
case 69888: case 69889: case 69890: return 1348 + c - 69888;
|
||||
case 69927: case 69928: case 69929: case 69930: case 69931: case 69932: case 69933: case 69934: case 69935: case 69936: case 69937: case 69938: case 69939: case 69940: return 1351 + c - 69927;
|
||||
case 70003: return 1365;
|
||||
case 70016 ... 70018: return 1366 + c - 70016;
|
||||
case 70067 ... 70080: return 1369 + c - 70067;
|
||||
case 70090 ... 70092: return 1383 + c - 70090;
|
||||
case 70188 ... 70199: return 1386 + c - 70188;
|
||||
case 70016: case 70017: case 70018: return 1366 + c - 70016;
|
||||
case 70067: case 70068: case 70069: case 70070: case 70071: case 70072: case 70073: case 70074: case 70075: case 70076: case 70077: case 70078: case 70079: case 70080: return 1369 + c - 70067;
|
||||
case 70090: case 70091: case 70092: return 1383 + c - 70090;
|
||||
case 70188: case 70189: case 70190: case 70191: case 70192: case 70193: case 70194: case 70195: case 70196: case 70197: case 70198: case 70199: return 1386 + c - 70188;
|
||||
case 70206: return 1398;
|
||||
case 70367 ... 70378: return 1399 + c - 70367;
|
||||
case 70400 ... 70403: return 1411 + c - 70400;
|
||||
case 70367: case 70368: case 70369: case 70370: case 70371: case 70372: case 70373: case 70374: case 70375: case 70376: case 70377: case 70378: return 1399 + c - 70367;
|
||||
case 70400: case 70401: case 70402: case 70403: return 1411 + c - 70400;
|
||||
case 70460: return 1415;
|
||||
case 70462 ... 70468: return 1416 + c - 70462;
|
||||
case 70471 ... 70472: return 1423 + c - 70471;
|
||||
case 70475 ... 70477: return 1425 + c - 70475;
|
||||
case 70462: case 70463: case 70464: case 70465: case 70466: case 70467: case 70468: return 1416 + c - 70462;
|
||||
case 70471: case 70472: return 1423 + c - 70471;
|
||||
case 70475: case 70476: case 70477: return 1425 + c - 70475;
|
||||
case 70487: return 1428;
|
||||
case 70498 ... 70499: return 1429 + c - 70498;
|
||||
case 70502 ... 70508: return 1431 + c - 70502;
|
||||
case 70512 ... 70516: return 1438 + c - 70512;
|
||||
case 70709 ... 70726: return 1443 + c - 70709;
|
||||
case 70832 ... 70851: return 1461 + c - 70832;
|
||||
case 71087 ... 71093: return 1481 + c - 71087;
|
||||
case 71096 ... 71104: return 1488 + c - 71096;
|
||||
case 71132 ... 71133: return 1497 + c - 71132;
|
||||
case 71216 ... 71232: return 1499 + c - 71216;
|
||||
case 71339 ... 71351: return 1516 + c - 71339;
|
||||
case 71453 ... 71467: return 1529 + c - 71453;
|
||||
case 72193 ... 72202: return 1544 + c - 72193;
|
||||
case 72243 ... 72249: return 1554 + c - 72243;
|
||||
case 72251 ... 72254: return 1561 + c - 72251;
|
||||
case 70498: case 70499: return 1429 + c - 70498;
|
||||
case 70502: case 70503: case 70504: case 70505: case 70506: case 70507: case 70508: return 1431 + c - 70502;
|
||||
case 70512: case 70513: case 70514: case 70515: case 70516: return 1438 + c - 70512;
|
||||
case 70709: case 70710: case 70711: case 70712: case 70713: case 70714: case 70715: case 70716: case 70717: case 70718: case 70719: case 70720: case 70721: case 70722: case 70723: case 70724: case 70725: case 70726: return 1443 + c - 70709;
|
||||
case 70832: case 70833: case 70834: case 70835: case 70836: case 70837: case 70838: case 70839: case 70840: case 70841: case 70842: case 70843: case 70844: case 70845: case 70846: case 70847: case 70848: case 70849: case 70850: case 70851: return 1461 + c - 70832;
|
||||
case 71087: case 71088: case 71089: case 71090: case 71091: case 71092: case 71093: return 1481 + c - 71087;
|
||||
case 71096: case 71097: case 71098: case 71099: case 71100: case 71101: case 71102: case 71103: case 71104: return 1488 + c - 71096;
|
||||
case 71132: case 71133: return 1497 + c - 71132;
|
||||
case 71216: case 71217: case 71218: case 71219: case 71220: case 71221: case 71222: case 71223: case 71224: case 71225: case 71226: case 71227: case 71228: case 71229: case 71230: case 71231: case 71232: return 1499 + c - 71216;
|
||||
case 71339: case 71340: case 71341: case 71342: case 71343: case 71344: case 71345: case 71346: case 71347: case 71348: case 71349: case 71350: case 71351: return 1516 + c - 71339;
|
||||
case 71453: case 71454: case 71455: case 71456: case 71457: case 71458: case 71459: case 71460: case 71461: case 71462: case 71463: case 71464: case 71465: case 71466: case 71467: return 1529 + c - 71453;
|
||||
case 72193: case 72194: case 72195: case 72196: case 72197: case 72198: case 72199: case 72200: case 72201: case 72202: return 1544 + c - 72193;
|
||||
case 72243: case 72244: case 72245: case 72246: case 72247: case 72248: case 72249: return 1554 + c - 72243;
|
||||
case 72251: case 72252: case 72253: case 72254: return 1561 + c - 72251;
|
||||
case 72263: return 1565;
|
||||
case 72273 ... 72283: return 1566 + c - 72273;
|
||||
case 72330 ... 72345: return 1577 + c - 72330;
|
||||
case 72751 ... 72758: return 1593 + c - 72751;
|
||||
case 72760 ... 72767: return 1601 + c - 72760;
|
||||
case 72850 ... 72871: return 1609 + c - 72850;
|
||||
case 72873 ... 72886: return 1631 + c - 72873;
|
||||
case 73009 ... 73014: return 1645 + c - 73009;
|
||||
case 72273: case 72274: case 72275: case 72276: case 72277: case 72278: case 72279: case 72280: case 72281: case 72282: case 72283: return 1566 + c - 72273;
|
||||
case 72330: case 72331: case 72332: case 72333: case 72334: case 72335: case 72336: case 72337: case 72338: case 72339: case 72340: case 72341: case 72342: case 72343: case 72344: case 72345: return 1577 + c - 72330;
|
||||
case 72751: case 72752: case 72753: case 72754: case 72755: case 72756: case 72757: case 72758: return 1593 + c - 72751;
|
||||
case 72760: case 72761: case 72762: case 72763: case 72764: case 72765: case 72766: case 72767: return 1601 + c - 72760;
|
||||
case 72850: case 72851: case 72852: case 72853: case 72854: case 72855: case 72856: case 72857: case 72858: case 72859: case 72860: case 72861: case 72862: case 72863: case 72864: case 72865: case 72866: case 72867: case 72868: case 72869: case 72870: case 72871: return 1609 + c - 72850;
|
||||
case 72873: case 72874: case 72875: case 72876: case 72877: case 72878: case 72879: case 72880: case 72881: case 72882: case 72883: case 72884: case 72885: case 72886: return 1631 + c - 72873;
|
||||
case 73009: case 73010: case 73011: case 73012: case 73013: case 73014: return 1645 + c - 73009;
|
||||
case 73018: return 1651;
|
||||
case 73020 ... 73021: return 1652 + c - 73020;
|
||||
case 73023 ... 73029: return 1654 + c - 73023;
|
||||
case 73020: case 73021: return 1652 + c - 73020;
|
||||
case 73023: case 73024: case 73025: case 73026: case 73027: case 73028: case 73029: return 1654 + c - 73023;
|
||||
case 73031: return 1661;
|
||||
case 92912 ... 92916: return 1662 + c - 92912;
|
||||
case 92976 ... 92982: return 1667 + c - 92976;
|
||||
case 94033 ... 94078: return 1674 + c - 94033;
|
||||
case 94095 ... 94098: return 1720 + c - 94095;
|
||||
case 113821 ... 113822: return 1724 + c - 113821;
|
||||
case 119141 ... 119145: return 1726 + c - 119141;
|
||||
case 119149 ... 119154: return 1731 + c - 119149;
|
||||
case 119163 ... 119170: return 1737 + c - 119163;
|
||||
case 119173 ... 119179: return 1745 + c - 119173;
|
||||
case 119210 ... 119213: return 1752 + c - 119210;
|
||||
case 119362 ... 119364: return 1756 + c - 119362;
|
||||
case 121344 ... 121398: return 1759 + c - 121344;
|
||||
case 121403 ... 121452: return 1814 + c - 121403;
|
||||
case 92912: case 92913: case 92914: case 92915: case 92916: return 1662 + c - 92912;
|
||||
case 92976: case 92977: case 92978: case 92979: case 92980: case 92981: case 92982: return 1667 + c - 92976;
|
||||
case 94033: case 94034: case 94035: case 94036: case 94037: case 94038: case 94039: case 94040: case 94041: case 94042: case 94043: case 94044: case 94045: case 94046: case 94047: case 94048: case 94049: case 94050: case 94051: case 94052: case 94053: case 94054: case 94055: case 94056: case 94057: case 94058: case 94059: case 94060: case 94061: case 94062: case 94063: case 94064: case 94065: case 94066: case 94067: case 94068: case 94069: case 94070: case 94071: case 94072: case 94073: case 94074: case 94075: case 94076: case 94077: case 94078: return 1674 + c - 94033;
|
||||
case 94095: case 94096: case 94097: case 94098: return 1720 + c - 94095;
|
||||
case 113821: case 113822: return 1724 + c - 113821;
|
||||
case 119141: case 119142: case 119143: case 119144: case 119145: return 1726 + c - 119141;
|
||||
case 119149: case 119150: case 119151: case 119152: case 119153: case 119154: return 1731 + c - 119149;
|
||||
case 119163: case 119164: case 119165: case 119166: case 119167: case 119168: case 119169: case 119170: return 1737 + c - 119163;
|
||||
case 119173: case 119174: case 119175: case 119176: case 119177: case 119178: case 119179: return 1745 + c - 119173;
|
||||
case 119210: case 119211: case 119212: case 119213: return 1752 + c - 119210;
|
||||
case 119362: case 119363: case 119364: return 1756 + c - 119362;
|
||||
case 121344: case 121345: case 121346: case 121347: case 121348: case 121349: case 121350: case 121351: case 121352: case 121353: case 121354: case 121355: case 121356: case 121357: case 121358: case 121359: case 121360: case 121361: case 121362: case 121363: case 121364: case 121365: case 121366: case 121367: case 121368: case 121369: case 121370: case 121371: case 121372: case 121373: case 121374: case 121375: case 121376: case 121377: case 121378: case 121379: case 121380: case 121381: case 121382: case 121383: case 121384: case 121385: case 121386: case 121387: case 121388: case 121389: case 121390: case 121391: case 121392: case 121393: case 121394: case 121395: case 121396: case 121397: case 121398: return 1759 + c - 121344;
|
||||
case 121403: case 121404: case 121405: case 121406: case 121407: case 121408: case 121409: case 121410: case 121411: case 121412: case 121413: case 121414: case 121415: case 121416: case 121417: case 121418: case 121419: case 121420: case 121421: case 121422: case 121423: case 121424: case 121425: case 121426: case 121427: case 121428: case 121429: case 121430: case 121431: case 121432: case 121433: case 121434: case 121435: case 121436: case 121437: case 121438: case 121439: case 121440: case 121441: case 121442: case 121443: case 121444: case 121445: case 121446: case 121447: case 121448: case 121449: case 121450: case 121451: case 121452: return 1814 + c - 121403;
|
||||
case 121461: return 1864;
|
||||
case 121476: return 1865;
|
||||
case 121499 ... 121503: return 1866 + c - 121499;
|
||||
case 121505 ... 121519: return 1871 + c - 121505;
|
||||
case 122880 ... 122886: return 1886 + c - 122880;
|
||||
case 122888 ... 122904: return 1893 + c - 122888;
|
||||
case 122907 ... 122913: return 1910 + c - 122907;
|
||||
case 122915 ... 122916: return 1917 + c - 122915;
|
||||
case 122918 ... 122922: return 1919 + c - 122918;
|
||||
case 125136 ... 125142: return 1924 + c - 125136;
|
||||
case 125252 ... 125258: return 1931 + c - 125252;
|
||||
case 917760 ... 917999: return 1938 + c - 917760;
|
||||
case 121499: case 121500: case 121501: case 121502: case 121503: return 1866 + c - 121499;
|
||||
case 121505: case 121506: case 121507: case 121508: case 121509: case 121510: case 121511: case 121512: case 121513: case 121514: case 121515: case 121516: case 121517: case 121518: case 121519: return 1871 + c - 121505;
|
||||
case 122880: case 122881: case 122882: case 122883: case 122884: case 122885: case 122886: return 1886 + c - 122880;
|
||||
case 122888: case 122889: case 122890: case 122891: case 122892: case 122893: case 122894: case 122895: case 122896: case 122897: case 122898: case 122899: case 122900: case 122901: case 122902: case 122903: case 122904: return 1893 + c - 122888;
|
||||
case 122907: case 122908: case 122909: case 122910: case 122911: case 122912: case 122913: return 1910 + c - 122907;
|
||||
case 122915: case 122916: return 1917 + c - 122915;
|
||||
case 122918: case 122919: case 122920: case 122921: case 122922: return 1919 + c - 122918;
|
||||
case 125136: case 125137: case 125138: case 125139: case 125140: case 125141: case 125142: return 1924 + c - 125136;
|
||||
case 125252: case 125253: case 125254: case 125255: case 125256: case 125257: case 125258: return 1931 + c - 125252;
|
||||
case 917760: case 917761: case 917762: case 917763: case 917764: case 917765: case 917766: case 917767: case 917768: case 917769: case 917770: case 917771: case 917772: case 917773: case 917774: case 917775: case 917776: case 917777: case 917778: case 917779: case 917780: case 917781: case 917782: case 917783: case 917784: case 917785: case 917786: case 917787: case 917788: case 917789: case 917790: case 917791: case 917792: case 917793: case 917794: case 917795: case 917796: case 917797: case 917798: case 917799: case 917800: case 917801: case 917802: case 917803: case 917804: case 917805: case 917806: case 917807: case 917808: case 917809: case 917810: case 917811: case 917812: case 917813: case 917814: case 917815: case 917816: case 917817: case 917818: case 917819: case 917820: case 917821: case 917822: case 917823: case 917824: case 917825: case 917826: case 917827: case 917828: case 917829: case 917830: case 917831: case 917832: case 917833: case 917834: case 917835: case 917836: case 917837: case 917838: case 917839: case 917840: case 917841: case 917842: case 917843: case 917844: case 917845: case 917846: case 917847: case 917848: case 917849: case 917850: case 917851: case 917852: case 917853: case 917854: case 917855: case 917856: case 917857: case 917858: case 917859: case 917860: case 917861: case 917862: case 917863: case 917864: case 917865: case 917866: case 917867: case 917868: case 917869: case 917870: case 917871: case 917872: case 917873: case 917874: case 917875: case 917876: case 917877: case 917878: case 917879: case 917880: case 917881: case 917882: case 917883: case 917884: case 917885: case 917886: case 917887: case 917888: case 917889: case 917890: case 917891: case 917892: case 917893: case 917894: case 917895: case 917896: case 917897: case 917898: case 917899: case 917900: case 917901: case 917902: case 917903: case 917904: case 917905: case 917906: case 917907: case 917908: case 917909: case 917910: case 917911: case 917912: case 917913: case 917914: case 917915: case 917916: case 917917: case 917918: case 917919: case 917920: case 917921: case 917922: case 917923: case 917924: case 917925: case 917926: case 917927: case 917928: case 917929: case 917930: case 917931: case 917932: case 917933: case 917934: case 917935: case 917936: case 917937: case 917938: case 917939: case 917940: case 917941: case 917942: case 917943: case 917944: case 917945: case 917946: case 917947: case 917948: case 917949: case 917950: case 917951: case 917952: case 917953: case 917954: case 917955: case 917956: case 917957: case 917958: case 917959: case 917960: case 917961: case 917962: case 917963: case 917964: case 917965: case 917966: case 917967: case 917968: case 917969: case 917970: case 917971: case 917972: case 917973: case 917974: case 917975: case 917976: case 917977: case 917978: case 917979: case 917980: case 917981: case 917982: case 917983: case 917984: case 917985: case 917986: case 917987: case 917988: case 917989: case 917990: case 917991: case 917992: case 917993: case 917994: case 917995: case 917996: case 917997: case 917998: case 917999: return 1938 + c - 917760;
|
||||
default: return 0;
|
||||
} // }}}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,14 @@ import fcntl
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import socket
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from contextlib import contextmanager
|
||||
from time import monotonic
|
||||
|
||||
from .constants import appname, is_macos, is_wayland
|
||||
from .constants import (
|
||||
appname, is_macos, is_wayland, supports_primary_selection
|
||||
)
|
||||
from .fast_data_types import (
|
||||
GLSL_VERSION, log_error_string, redirect_std_streams, x11_display,
|
||||
x11_window_id
|
||||
@@ -80,8 +78,50 @@ def parse_color_set(raw):
|
||||
continue
|
||||
|
||||
|
||||
def screen_size_function(fd=None):
|
||||
ans = getattr(screen_size_function, 'ans', None)
|
||||
if ans is None:
|
||||
from collections import namedtuple
|
||||
import array
|
||||
import fcntl
|
||||
import termios
|
||||
Size = namedtuple('Size', 'rows cols width height cell_width cell_height')
|
||||
if fd is None:
|
||||
fd = sys.stdout
|
||||
|
||||
def screen_size():
|
||||
if screen_size.changed:
|
||||
buf = array.array('H', [0, 0, 0, 0])
|
||||
fcntl.ioctl(fd, termios.TIOCGWINSZ, buf)
|
||||
rows, cols, width, height = tuple(buf)
|
||||
cell_width, cell_height = width // (cols or 1), height // (rows or 1)
|
||||
screen_size.ans = Size(rows, cols, width, height, cell_width, cell_height)
|
||||
screen_size.changed = False
|
||||
return screen_size.ans
|
||||
screen_size.changed = True
|
||||
screen_size.Size = Size
|
||||
ans = screen_size_function.ans = screen_size
|
||||
|
||||
return ans
|
||||
|
||||
|
||||
def fit_image(width, height, pwidth, pheight):
|
||||
from math import floor
|
||||
if height > pheight:
|
||||
corrf = pheight / float(height)
|
||||
width, height = floor(corrf * width), pheight
|
||||
if width > pwidth:
|
||||
corrf = pwidth / float(width)
|
||||
width, height = pwidth, floor(corrf * height)
|
||||
if height > pheight:
|
||||
corrf = pheight / float(height)
|
||||
width, height = floor(corrf * width), pheight
|
||||
|
||||
return int(width), int(height)
|
||||
|
||||
|
||||
def set_primary_selection(text):
|
||||
if is_macos or is_wayland:
|
||||
if not supports_primary_selection:
|
||||
return # There is no primary selection
|
||||
if isinstance(text, bytes):
|
||||
text = text.decode('utf-8')
|
||||
@@ -90,7 +130,7 @@ def set_primary_selection(text):
|
||||
|
||||
|
||||
def get_primary_selection():
|
||||
if is_macos or is_wayland:
|
||||
if not supports_primary_selection:
|
||||
return '' # There is no primary selection
|
||||
from kitty.fast_data_types import get_primary_selection
|
||||
return (get_primary_selection() or b'').decode('utf-8', 'replace')
|
||||
@@ -111,22 +151,26 @@ def base64_encode(
|
||||
|
||||
|
||||
def command_for_open(program='default'):
|
||||
if program == 'default':
|
||||
if isinstance(program, str):
|
||||
from .config_utils import to_cmdline
|
||||
program = to_cmdline(program)
|
||||
if program == ['default']:
|
||||
cmd = ['open'] if is_macos else ['xdg-open']
|
||||
else:
|
||||
cmd = shlex.split(program)
|
||||
cmd = program
|
||||
return cmd
|
||||
|
||||
|
||||
def open_cmd(cmd, arg=None):
|
||||
def open_cmd(cmd, arg=None, cwd=None):
|
||||
import subprocess
|
||||
if arg is not None:
|
||||
cmd = list(cmd)
|
||||
cmd.append(arg)
|
||||
return subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
return subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cwd or None)
|
||||
|
||||
|
||||
def open_url(url, program='default'):
|
||||
return open_cmd(command_for_open(program), url)
|
||||
def open_url(url, program='default', cwd=None):
|
||||
return open_cmd(command_for_open(program), url, cwd=cwd)
|
||||
|
||||
|
||||
def detach(fork=True, setsid=True, redirect=True):
|
||||
@@ -199,6 +243,8 @@ def remove_socket_file(s, path=None):
|
||||
|
||||
|
||||
def single_instance_unix(name):
|
||||
import socket
|
||||
import tempfile
|
||||
home = os.path.expanduser('~')
|
||||
candidates = [tempfile.gettempdir(), home]
|
||||
if is_macos:
|
||||
@@ -237,6 +283,7 @@ def single_instance_unix(name):
|
||||
|
||||
|
||||
def single_instance(group_id=None):
|
||||
import socket
|
||||
name = '{}-ipc-{}'.format(appname, os.geteuid())
|
||||
if group_id:
|
||||
name += '-{}'.format(group_id)
|
||||
@@ -262,6 +309,7 @@ def single_instance(group_id=None):
|
||||
|
||||
|
||||
def parse_address_spec(spec):
|
||||
import socket
|
||||
protocol, rest = spec.split(':', 1)
|
||||
socket_path = None
|
||||
if protocol == 'unix':
|
||||
|
||||
2
kitty/wcwidth-std.h
generated
2
kitty/wcwidth-std.h
generated
@@ -1,4 +1,4 @@
|
||||
// unicode data, built from the unicode standard on: 2018-02-09
|
||||
// unicode data, built from the unicode standard on: 2018-05-01
|
||||
// see gen-wcwidth.py
|
||||
#pragma once
|
||||
#include "data-types.h"
|
||||
|
||||
108
kitty/window.py
108
kitty/window.py
@@ -8,24 +8,27 @@ import weakref
|
||||
from collections import deque
|
||||
from enum import Enum
|
||||
|
||||
from .config import build_ansi_color_table, parse_send_text_bytes
|
||||
from .child import cwd_of_process
|
||||
from .config import build_ansi_color_table
|
||||
from .constants import (
|
||||
ScreenGeometry, WindowGeometry, appname, get_boss, wakeup
|
||||
)
|
||||
from .fast_data_types import (
|
||||
BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, CELL_PROGRAM,
|
||||
CELL_SPECIAL_PROGRAM, CSI, CURSOR_PROGRAM, DCS, GRAPHICS_PREMULT_PROGRAM,
|
||||
GRAPHICS_PROGRAM, OSC, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, Screen,
|
||||
add_window, compile_program, glfw_post_empty_event, init_cell_program,
|
||||
init_cursor_program, set_clipboard_string, set_window_render_data,
|
||||
update_window_title, update_window_visibility, viewport_for_window
|
||||
CELL_SPECIAL_PROGRAM, CSI, CURSOR_PROGRAM, DCS, DECORATION, DIM,
|
||||
GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM, OSC, REVERSE, SCROLL_FULL,
|
||||
SCROLL_LINE, SCROLL_PAGE, STRIKETHROUGH, Screen, add_window,
|
||||
compile_program, get_clipboard_string, glfw_post_empty_event,
|
||||
init_cell_program, init_cursor_program, set_clipboard_string,
|
||||
set_titlebar_color, set_window_render_data, update_window_title,
|
||||
update_window_visibility, viewport_for_window
|
||||
)
|
||||
from .keys import keyboard_mode_name
|
||||
from .rgb import to_color
|
||||
from .terminfo import get_capabilities
|
||||
from .utils import (
|
||||
color_as_int, load_shaders, open_cmd, open_url, parse_color_set,
|
||||
sanitize_title
|
||||
color_as_int, get_primary_selection, load_shaders, log_error, open_cmd,
|
||||
open_url, parse_color_set, sanitize_title, set_primary_selection
|
||||
)
|
||||
|
||||
|
||||
@@ -62,6 +65,10 @@ def load_shader_programs(semi_transparent=0):
|
||||
'FOREGROUND': CELL_FG_PROGRAM,
|
||||
}.items():
|
||||
vv, ff = v.replace('WHICH_PROGRAM', which), f.replace('WHICH_PROGRAM', which)
|
||||
shifts = '\n'.join('#define {} {}'.format(name, val) for name, val in (
|
||||
('DECORATION_SHIFT', DECORATION), ('REVERSE_SHIFT', REVERSE), ('STRIKE_SHIFT', STRIKETHROUGH), ('DIM_SHIFT', DIM),
|
||||
))
|
||||
vv = vv.replace('#define SHIFTS', shifts)
|
||||
if semi_transparent:
|
||||
vv = vv.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT')
|
||||
ff = ff.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT')
|
||||
@@ -88,6 +95,7 @@ class Window:
|
||||
|
||||
def __init__(self, tab, child, opts, args, override_title=None):
|
||||
self.action_on_close = None
|
||||
self.needs_attention = False
|
||||
self.override_title = override_title
|
||||
self.overlay_window_id = None
|
||||
self.overlay_for = None
|
||||
@@ -186,10 +194,9 @@ class Window:
|
||||
required_mode = frozenset(required_mode.split(','))
|
||||
if not required_mode & {mode, 'all'}:
|
||||
return True
|
||||
data = parse_send_text_bytes(text)
|
||||
if not data:
|
||||
if not text:
|
||||
return True
|
||||
self.write_to_child(data)
|
||||
self.write_to_child(text)
|
||||
|
||||
def write_to_child(self, data):
|
||||
if data:
|
||||
@@ -213,6 +220,7 @@ class Window:
|
||||
|
||||
def focus_changed(self, focused):
|
||||
if focused:
|
||||
self.needs_attention = False
|
||||
if self.screen.focus_tracking_enabled:
|
||||
self.screen.send_escape_code_to_child(CSI, 'I')
|
||||
else:
|
||||
@@ -227,6 +235,26 @@ class Window:
|
||||
def icon_changed(self, new_icon):
|
||||
pass # TODO: Implement this
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return get_boss().active_window is self
|
||||
|
||||
def on_bell(self):
|
||||
if not self.is_active:
|
||||
self.needs_attention = True
|
||||
tab = self.tabref()
|
||||
if tab is not None:
|
||||
tab.on_bell(self)
|
||||
|
||||
def change_titlebar_color(self):
|
||||
val = self.opts.macos_titlebar_color
|
||||
if val:
|
||||
if (val & 0xff) == 1:
|
||||
val = self.screen.color_profile.default_bg
|
||||
else:
|
||||
val = val >> 8
|
||||
set_titlebar_color(self.os_window_id, val)
|
||||
|
||||
def change_colors(self, changes):
|
||||
dirtied = default_bg_changed = False
|
||||
|
||||
@@ -305,9 +333,46 @@ class Window:
|
||||
def handle_remote_cmd(self, cmd):
|
||||
get_boss().handle_remote_cmd(cmd, self)
|
||||
|
||||
def handle_remote_print(self, msg):
|
||||
from base64 import standard_b64decode
|
||||
msg = standard_b64decode(msg).decode('utf-8')
|
||||
print(msg, end='', file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
|
||||
def send_cmd_response(self, response):
|
||||
self.screen.send_escape_code_to_child(DCS, '@kitty-cmd' + json.dumps(response))
|
||||
|
||||
def clipboard_control(self, data):
|
||||
where, text = data.partition(';')[::2]
|
||||
if text == '?':
|
||||
response = None
|
||||
if 'read-clipboard' in self.opts.clipboard_control and ('s' in where or 'c' in where):
|
||||
response = get_clipboard_string()
|
||||
loc = 'c'
|
||||
elif 'p' in where and 'read-primary' in self.opts.clipboard_control:
|
||||
response = get_primary_selection()
|
||||
loc = 'p'
|
||||
if response is not None:
|
||||
from base64 import standard_b64encode
|
||||
self.screen.send_escape_code_to_child(OSC, '{};{}'.format(
|
||||
loc, standard_b64encode(response.encode('utf-8')).decode('ascii')))
|
||||
|
||||
else:
|
||||
from base64 import standard_b64decode
|
||||
try:
|
||||
text = standard_b64decode(text).decode('utf-8')
|
||||
except Exception:
|
||||
log_error('Invalid data to write to clipboard received, ignoring')
|
||||
return
|
||||
if 's' in where or 'c' in where:
|
||||
if 'write-clipboard' in self.opts.clipboard_control:
|
||||
set_clipboard_string(text)
|
||||
if 'p' in where:
|
||||
if self.opts.copy_on_select:
|
||||
if 'write-clipboard' in self.opts.clipboard_control:
|
||||
set_clipboard_string(text)
|
||||
if 'write-primary' in self.opts.clipboard_control:
|
||||
set_primary_selection(text)
|
||||
# }}}
|
||||
|
||||
def text_for_selection(self):
|
||||
@@ -320,15 +385,25 @@ class Window:
|
||||
self.screen.reset_callbacks()
|
||||
self.screen = None
|
||||
|
||||
def as_text(self, as_ansi=False, add_history=False):
|
||||
def as_text(self, as_ansi=False, add_history=False, add_wrap_markers=False):
|
||||
lines = []
|
||||
self.screen.as_text(lines.append, as_ansi)
|
||||
if add_history and not self.screen.is_using_alternate_linebuf():
|
||||
add_history = add_history and not self.screen.is_using_alternate_linebuf()
|
||||
f = self.screen.as_text_non_visual if add_history else self.screen.as_text
|
||||
f(lines.append, as_ansi, add_wrap_markers)
|
||||
if add_history:
|
||||
h = []
|
||||
self.screen.historybuf.as_text(h.append, as_ansi)
|
||||
lines = h + lines
|
||||
return ''.join(lines)
|
||||
|
||||
@property
|
||||
def cwd_of_child(self):
|
||||
# TODO: Maybe use the cwd of the leader of the foreground process
|
||||
# group in the session of the child process?
|
||||
pid = self.child.pid
|
||||
if pid is not None:
|
||||
return cwd_of_process(pid) or None
|
||||
|
||||
# actions {{{
|
||||
|
||||
def show_scrollback(self):
|
||||
@@ -348,12 +423,13 @@ class Window:
|
||||
set_clipboard_string(text)
|
||||
|
||||
def pass_selection_to_program(self, *args):
|
||||
cwd = self.cwd_of_child
|
||||
text = self.text_for_selection()
|
||||
if text:
|
||||
if args:
|
||||
open_cmd(args, text)
|
||||
open_cmd(args, text, cwd=cwd)
|
||||
else:
|
||||
open_url(text)
|
||||
open_url(text, cwd=cwd)
|
||||
|
||||
def scroll_line_up(self):
|
||||
if self.screen.is_main_linebuf():
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user