mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-15 04:57:52 +02:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a3529b7c2 | ||
|
|
a2671c9101 | ||
|
|
9e514df604 | ||
|
|
8865d3231a | ||
|
|
5fb661d72d | ||
|
|
fd12c5a1e0 | ||
|
|
5008b89804 | ||
|
|
706cde84ae | ||
|
|
d9cd92d4ed | ||
|
|
4b41a7d182 | ||
|
|
fcce5c9a64 | ||
|
|
4c37fff496 | ||
|
|
450aa59303 | ||
|
|
77156be0f4 | ||
|
|
f37cce2ae4 | ||
|
|
dde80b9ad6 | ||
|
|
1aaaa3f1e9 | ||
|
|
e04e5a157f | ||
|
|
ed32bf35a7 | ||
|
|
ef7d4934d2 | ||
|
|
bda9155be1 | ||
|
|
3fef881956 | ||
|
|
2502111697 | ||
|
|
acd6b168fd | ||
|
|
164dd0d637 | ||
|
|
ee3118ffaf | ||
|
|
4f277deb6d | ||
|
|
f16e9500f1 | ||
|
|
45bfeeef21 | ||
|
|
d96fdb80ed | ||
|
|
0c0c6b732f | ||
|
|
537475d5bb | ||
|
|
8e7b6ad3c3 | ||
|
|
68b861b188 | ||
|
|
59e4c6660e | ||
|
|
38be3e98a1 | ||
|
|
4af1a38507 | ||
|
|
70110d54b0 | ||
|
|
76c6f91685 | ||
|
|
3d6c3a9979 | ||
|
|
7b3513d010 | ||
|
|
c280a28155 | ||
|
|
24598b846c | ||
|
|
dc43f0d42f | ||
|
|
5fede41205 | ||
|
|
6619bf33b0 | ||
|
|
627c80125b | ||
|
|
38bac98c12 | ||
|
|
2e4f3dab41 | ||
|
|
911c80aa3b | ||
|
|
fd85dfb417 | ||
|
|
b26c4c16d0 | ||
|
|
c650bd0aac | ||
|
|
fc1331cfdc | ||
|
|
fe4fdaefb9 | ||
|
|
704ae40dee | ||
|
|
0a2f164062 | ||
|
|
eb05f6864f | ||
|
|
b21bbbe14c | ||
|
|
18e5b74699 | ||
|
|
d16a29b942 | ||
|
|
dda5771ccd | ||
|
|
65c777e335 | ||
|
|
a8633756de | ||
|
|
1a32e62ebf | ||
|
|
7faf216f9e | ||
|
|
6bafdedd65 | ||
|
|
bd036040a6 | ||
|
|
c94cfdbf65 | ||
|
|
35766a1a86 | ||
|
|
616a104cce | ||
|
|
a5e74d406c | ||
|
|
30c37a5809 |
@@ -153,15 +153,6 @@
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "setuptools",
|
||||
"unix": {
|
||||
"filename": "setuptools-53.0.0.tar.gz",
|
||||
"hash": "sha256:1b18ef17d74ba97ac9c0e4b4265f123f07a8ae85d9cd093949fa056d3eeeead5",
|
||||
"urls": ["pypi"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "libpng",
|
||||
"unix": {
|
||||
|
||||
@@ -2,20 +2,16 @@
|
||||
|
||||
import subprocess
|
||||
|
||||
ignored = []
|
||||
for line in subprocess.check_output(['git', 'status', '--ignored', '--porcelain']).decode().splitlines():
|
||||
if line.startswith('!! '):
|
||||
ignored.append(line[3:])
|
||||
files_to_exclude = '\n'.join(ignored)
|
||||
|
||||
ls_files = subprocess.check_output([ 'git', 'ls-files']).decode('utf-8')
|
||||
all_files = set(ls_files.splitlines())
|
||||
all_files.discard('')
|
||||
cp = subprocess.run(['git', 'check-attr', 'linguist-generated', '--stdin'],
|
||||
check=True, stdout=subprocess.PIPE, input=subprocess.check_output([ 'git', 'ls-files']))
|
||||
check=True, stdout=subprocess.PIPE, input='\n'.join(all_files).encode('utf-8'))
|
||||
for line in cp.stdout.decode().splitlines():
|
||||
if line.endswith(' true'):
|
||||
files_to_exclude += '\n' + line.split(':')[0]
|
||||
fname = line.split(':', 1)[0]
|
||||
all_files.discard(fname)
|
||||
|
||||
p = subprocess.Popen([
|
||||
'cloc', '--exclude-list-file', '/dev/stdin', 'kitty', 'kittens', 'tools', 'kitty_tests', 'docs',
|
||||
], stdin=subprocess.PIPE)
|
||||
p.communicate(files_to_exclude.encode('utf-8'))
|
||||
raise SystemExit(p.wait())
|
||||
all_files -= {'nerd-fonts-glyphs.txt', 'rowcolumn-diacritics.txt'}
|
||||
cp = subprocess.run(['cloc', '--list-file', '-'], input='\n'.join(all_files).encode())
|
||||
raise SystemExit(cp.returncode)
|
||||
|
||||
@@ -43,12 +43,30 @@ The :doc:`ssh kitten <kittens/ssh>` is redesigned with powerful new features:
|
||||
Detailed list of changes
|
||||
-------------------------------------
|
||||
|
||||
0.30.0 [future]
|
||||
0.30.1 [2023-10-05]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Shell integration: Automatically alias sudo to make the kitty terminfo files available in the sudo environment. Can be turned off via :opt:`shell_integration`
|
||||
|
||||
- ssh kitten: Fix a regression in 0.28.0 that caused using ``--kitten`` to
|
||||
override :file:`ssh.conf` not inheriting settings from :file:`ssh.conf`
|
||||
(:iss:`6639`)
|
||||
|
||||
- themes kitten: Allow absolute paths for ``--config-file-name`` (:iss:`6638`)
|
||||
|
||||
- Expand environment variables in the :opt:`shell` option (:iss:`6511`)
|
||||
|
||||
- macOS: When running the default shell, run it via the login program so that calls to ``getlogin()`` work (:iss:`6511`)
|
||||
|
||||
- X11: Fix a crash on startup when the ibus service returns errors and the GLFW_IM_MODULE env var is set to ibus (:iss:`6650`)
|
||||
|
||||
|
||||
0.30.0 [2023-09-18]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- A new :doc:`transfer kitten </kittens/transfer>` that can be used to transfer files efficiently over the TTY device
|
||||
|
||||
- ssh kitten: A new configuration directive `to automatically forward the kitty remote control socket <kitten-ssh.forward_remote_control>`
|
||||
- ssh kitten: A new configuration directive :opt:`to automatically forward the kitty remote control socket <kitten-ssh.forward_remote_control>`
|
||||
|
||||
- Allow :doc:`easily building kitty from source </build>` needing the installation of only C and Go compilers.
|
||||
All other dependencies are automatically vendored
|
||||
|
||||
34
docs/conf.py
34
docs/conf.py
@@ -12,7 +12,7 @@ import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from functools import partial
|
||||
from functools import lru_cache, partial
|
||||
from typing import Any, Callable, Dict, Iterable, List, Tuple
|
||||
|
||||
from docutils import nodes
|
||||
@@ -546,9 +546,41 @@ def add_html_context(app: Any, pagename: str, templatename: str, context: Any, d
|
||||
context['toctree'] = include_sub_headings
|
||||
|
||||
|
||||
@lru_cache
|
||||
def monkeypatch_man_writer() -> None:
|
||||
'''
|
||||
Monkeypatch the docutils man translator to output better tables
|
||||
'''
|
||||
from docutils.writers.manpage import Table, Translator
|
||||
|
||||
class PatchedTable(Table): # type: ignore
|
||||
_options: list[str]
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.needs_border_removal = self._options == ['center']
|
||||
if self.needs_border_removal:
|
||||
self._options = ['box', 'center']
|
||||
|
||||
def as_list(self) -> list[str]:
|
||||
ans: list[str] = super().as_list()
|
||||
if self.needs_border_removal:
|
||||
# remove side and top borders as we use box in self._options
|
||||
ans[2] = ans[2][1:]
|
||||
a, b = ans[2].rpartition('|')[::2]
|
||||
ans[2] = a + b
|
||||
if ans[3] == '_\n':
|
||||
del ans[3] # top border
|
||||
del ans[-2] # bottom border
|
||||
return ans
|
||||
def visit_table(self: Translator, node: object) -> None:
|
||||
setattr(self, '_active_table', PatchedTable())
|
||||
setattr(Translator, 'visit_table', visit_table)
|
||||
|
||||
|
||||
def setup(app: Any) -> None:
|
||||
os.makedirs('generated/conf', exist_ok=True)
|
||||
from kittens.runner import all_kitten_names
|
||||
monkeypatch_man_writer()
|
||||
kn = all_kitten_names()
|
||||
write_cli_docs(kn)
|
||||
write_remote_control_protocol_docs()
|
||||
|
||||
@@ -32,8 +32,8 @@ Comments can be added to the config file as lines starting with the ``#``
|
||||
character. This works only if the ``#`` character is the first character in the
|
||||
line.
|
||||
|
||||
Lines can be split by starting the next line with the ``\\`` character.
|
||||
All leading whitespace and the ``\\`` character are removed.
|
||||
Lines can be split by starting the next line with the ``\`` character.
|
||||
All leading whitespace and the ``\`` character are removed.
|
||||
|
||||
.. _include:
|
||||
|
||||
|
||||
23
docs/faq.rst
23
docs/faq.rst
@@ -99,7 +99,7 @@ These issues all have the same root cause: the kitty terminfo files not being
|
||||
available. The most common way this happens is SSHing into a computer that does
|
||||
not have the kitty terminfo files. The simplest fix for that is running::
|
||||
|
||||
kitty +kitten ssh myserver
|
||||
kitten ssh myserver
|
||||
|
||||
It will automatically copy over the terminfo files and also magically enable
|
||||
:doc:`shell integration </shell-integration>` on the remote machine.
|
||||
@@ -108,7 +108,7 @@ This :doc:`ssh kitten <kittens/ssh>` takes all the same command line arguments
|
||||
as :program:`ssh`, you can alias it to something small in your shell's rc files
|
||||
to avoid having to type it each time::
|
||||
|
||||
alias s="kitty +kitten ssh"
|
||||
alias s="kitten ssh"
|
||||
|
||||
If this does not work, see :ref:`manual_terminfo_copy` for alternative ways to
|
||||
get the kitty terminfo files onto a remote computer.
|
||||
@@ -130,12 +130,15 @@ by running ``sudo visudo`` and adding the following line::
|
||||
|
||||
Defaults env_keep += "TERM TERMINFO"
|
||||
|
||||
If none of these are suitable for you, you can run sudo as follows::
|
||||
If none of these are suitable for you, you can run sudo as ::
|
||||
|
||||
sudo TERMINFO="$TERMINFO" -s -H
|
||||
sudo TERMINFO="$TERMINFO"
|
||||
|
||||
This will start a new root shell with the correct :envvar:`TERMINFO` value from your
|
||||
current environment copied over.
|
||||
This will make :envvar:`TERMINFO` available
|
||||
in the sudo environment. Create an alias in your shell rc files to make this
|
||||
convenient::
|
||||
|
||||
alias sudo="sudo TERMINFO=\"$TERMINFO\""
|
||||
|
||||
If you have double width characters in your prompt, you may also need to
|
||||
explicitly set a UTF-8 locale, like::
|
||||
@@ -148,7 +151,7 @@ I cannot use the key combination X in program Y?
|
||||
|
||||
First, run::
|
||||
|
||||
kitty +kitten show_key -m kitty
|
||||
kitten show_key -m kitty
|
||||
|
||||
Press the key combination X. If the kitten reports the key press
|
||||
that means kitty is correctly sending the key press to terminal programs.
|
||||
@@ -172,7 +175,7 @@ How do I change the colors in a running kitty instance?
|
||||
The easiest way to do it is to use the :doc:`themes kitten </kittens/themes>`,
|
||||
to choose a new color theme. Simply run::
|
||||
|
||||
kitty +kitten themes
|
||||
kitten themes
|
||||
|
||||
And choose your theme from the list.
|
||||
|
||||
@@ -391,7 +394,7 @@ For example::
|
||||
This maps :kbd:`alt+s` to :kbd:`ctrl+s`. To figure out what bytes to use for
|
||||
the :sc:`send_text <send_text>` you can use the ``show_key`` kitten. Run::
|
||||
|
||||
kitty +kitten show_key
|
||||
kitten show_key
|
||||
|
||||
Then press the key you want to emulate. Note that this kitten will only show
|
||||
keys that actually reach the terminal program, in particular, keys mapped to
|
||||
@@ -399,7 +402,7 @@ actions in kitty will not be shown. To check those first map them to
|
||||
:ac:`no_op`. You can also start a kitty instance without any shortcuts to
|
||||
interfere::
|
||||
|
||||
kitty -o clear_all_shortcuts=yes kitty +kitten show_key
|
||||
kitty -o clear_all_shortcuts=yes kitten show_key
|
||||
|
||||
|
||||
How do I open a new window or tab with the same working directory as the current window?
|
||||
|
||||
@@ -16,7 +16,7 @@ For some discussion regarding the design choices, see :iss:`33`.
|
||||
|
||||
To see a quick demo, inside a |kitty| terminal run::
|
||||
|
||||
kitty +kitten icat path/to/some/image.png
|
||||
kitten icat path/to/some/image.png
|
||||
|
||||
You can also see a screenshot with more sophisticated features such as
|
||||
alpha-blending and text over graphics.
|
||||
@@ -524,8 +524,8 @@ Unicode placeholders
|
||||
|
||||
You can also use a special Unicode character ``U+10EEEE`` as a placeholder for
|
||||
an image. This approach is less flexible, but it allows using images inside
|
||||
any host application that supports Unicode and foreground colors (tmux, vim, weechat, etc.)
|
||||
and as a way to pass escape codes through to the underlying terminal.
|
||||
any host application that supports Unicode, foreground colors (tmux, vim, weechat, etc.),
|
||||
and a way to pass escape codes through to the underlying terminal.
|
||||
|
||||
The central idea is that we use a single *Private Use* Unicode character as a
|
||||
*placeholder* to indicate to the terminal that an image is supposed to be
|
||||
|
||||
@@ -144,7 +144,7 @@ kitty with the following bash snippet:
|
||||
set terminal pngcairo enhanced font 'Fira Sans,10'
|
||||
set autoscale
|
||||
set samples 1000
|
||||
set output '|kitty +kitten icat --stdin yes'
|
||||
set output '|kitten icat --stdin yes'
|
||||
set object 1 rectangle from screen 0,0 to screen 1,1 fillcolor rgb"#fdf6e3" behind
|
||||
plot $@
|
||||
set output '/dev/null'
|
||||
|
||||
@@ -58,15 +58,15 @@ Timestamps for the above video:
|
||||
14:15
|
||||
Interactive Kitty Shell: :kbd:`Ctrl+Shift+Esc`
|
||||
14:36
|
||||
Broadcast text: ``launch --allow-remote-control kitty +kitten broadcast``
|
||||
Broadcast text: ``launch --allow-remote-control kitten broadcast``
|
||||
15:18
|
||||
Kitty Remote Control Protocol
|
||||
15:52
|
||||
Interactive Kitty Shell: Help
|
||||
16:34
|
||||
Choose theme interactively: ``kitty +kitten themes -h``
|
||||
Choose theme interactively: ``kitten themes -h``
|
||||
17:23
|
||||
Choose theme by name: ``kitty +kitten themes [options] [theme_name]``
|
||||
Choose theme by name: ``kitten themes [options] [theme_name]``
|
||||
|
||||
.. raw:: html
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ issues in that proposal, listed at the :ref:`bottom of this document
|
||||
|
||||
You can see this protocol with all enhancements in action by running::
|
||||
|
||||
kitty +kitten show_key -m kitty
|
||||
kitten show_key -m kitty
|
||||
|
||||
inside the kitty terminal to report key events.
|
||||
|
||||
@@ -48,6 +48,7 @@ In addition to kitty, this protocol is also implemented in:
|
||||
* The `Helix text editor <https://github.com/helix-editor/helix/pull/4939>`__
|
||||
* The `far2l file manager <https://github.com/elfmz/far2l/commit/e1f2ee0ef2b8332e5fa3ad7f2e4afefe7c96fc3b>`__
|
||||
* The `awrit web browser <https://github.com/chase/awrit>`__
|
||||
* The `nushell shell <https://github.com/nushell/nushell/pull/10540>`__
|
||||
|
||||
.. versionadded:: 0.20.0
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ clipboard
|
||||
The ``clipboard`` kitten can be used to read or write to the system clipboard
|
||||
from the shell. It even works over SSH. Using it is as simple as::
|
||||
|
||||
echo hooray | kitty +kitten clipboard
|
||||
echo hooray | kitten clipboard
|
||||
|
||||
All text received on :file:`STDIN` is copied to the clipboard.
|
||||
|
||||
To get text from the clipboard::
|
||||
|
||||
kitty +kitten clipboard --get-clipboard
|
||||
kitten clipboard --get-clipboard
|
||||
|
||||
The text will be written to :file:`STDOUT`. Note that by default kitty asks for
|
||||
permission when a program attempts to read the clipboard. This can be
|
||||
@@ -29,22 +29,22 @@ more than just plain text from the system clipboard. You can transfer arbitrary
|
||||
data types. Best illustrated with some examples::
|
||||
|
||||
# Copy an image to the clipboard:
|
||||
kitty +kitten clipboard picture.png
|
||||
kitten clipboard picture.png
|
||||
|
||||
# Copy an image and some text to the clipboard:
|
||||
kitty +kitten clipboard picture.jpg text.txt
|
||||
kitten clipboard picture.jpg text.txt
|
||||
|
||||
# Copy text from STDIN and an image to the clipboard:
|
||||
echo hello | kitty +kitten clipboard picture.png /dev/stdin
|
||||
echo hello | kitten clipboard picture.png /dev/stdin
|
||||
|
||||
# Copy any raster image available on the clipboard to a PNG file:
|
||||
kitty +kitten clipboard -g picture.png
|
||||
kitten clipboard -g picture.png
|
||||
|
||||
# Copy an image to a file and text to STDOUT:
|
||||
kitty +kitten clipboard -g picture.png /dev/stdout
|
||||
kitten clipboard -g picture.png /dev/stdout
|
||||
|
||||
# List the formats available on the system clipboard
|
||||
kitty +kitten clipboard -g -m . /dev/stdout
|
||||
kitten clipboard -g -m . /dev/stdout
|
||||
|
||||
Normally, the kitten guesses MIME types based on the file names. To control the
|
||||
MIME types precisely, use the :option:`--mime <kitty +kitten clipboard --mime>` option.
|
||||
|
||||
@@ -39,7 +39,7 @@ Usage
|
||||
|
||||
In the kitty terminal, run::
|
||||
|
||||
kitty +kitten diff file1 file2
|
||||
kitten diff file1 file2
|
||||
|
||||
to see the diff between :file:`file1` and :file:`file2`.
|
||||
|
||||
@@ -48,7 +48,7 @@ example:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
alias d="kitty +kitten diff"
|
||||
alias d="kitten diff"
|
||||
|
||||
Now all you need to do to diff two files is::
|
||||
|
||||
@@ -103,9 +103,9 @@ Add the following to :file:`~/.gitconfig`:
|
||||
prompt = false
|
||||
trustExitCode = true
|
||||
[difftool "kitty"]
|
||||
cmd = kitty +kitten diff $LOCAL $REMOTE
|
||||
cmd = kitten diff $LOCAL $REMOTE
|
||||
[difftool "kitty.gui"]
|
||||
cmd = kitty kitty +kitten diff $LOCAL $REMOTE
|
||||
cmd = kitten diff $LOCAL $REMOTE
|
||||
|
||||
Now to use kitty-diff to view git diffs, you can simply do::
|
||||
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
Hyperlinked grep
|
||||
=================
|
||||
|
||||
.. note::
|
||||
|
||||
As of ripgrep versions newer that 13.0 it supports hyperlinks
|
||||
natively so you can just add the following alias in your shell rc file:
|
||||
``alias rg="rg --hyperlink-format=kitty"`` no need to use this kitten.
|
||||
But, see below for instructions on how to customize kitty to have it open
|
||||
the hyperlinks from ripgrep in your favorite editor.
|
||||
|
||||
This kitten allows you to search your files using `ripgrep
|
||||
<https://github.com/BurntSushi/ripgrep>`__ and open the results directly in your
|
||||
favorite editor in the terminal, at the line containing the search result,
|
||||
@@ -26,7 +34,7 @@ following contents:
|
||||
|
||||
Now, run a search with::
|
||||
|
||||
kitty +kitten hyperlinked_grep something
|
||||
kitten hyperlinked_grep something
|
||||
|
||||
Hold down the :kbd:`Ctrl+Shift` keys and click on any of the result lines, to
|
||||
open the file in :program:`vim` at the matching line. If you use some editor
|
||||
@@ -36,7 +44,7 @@ accordingly.
|
||||
Finally, add an alias to your shell's rc files to invoke the kitten as
|
||||
:command:`hg`::
|
||||
|
||||
alias hg="kitty +kitten hyperlinked_grep"
|
||||
alias hg="kitten hyperlinked_grep"
|
||||
|
||||
|
||||
You can now run searches with::
|
||||
|
||||
@@ -6,7 +6,7 @@ icat
|
||||
The ``icat`` kitten can be used to display arbitrary images in the |kitty|
|
||||
terminal. Using it is as simple as::
|
||||
|
||||
kitty +kitten icat image.jpeg
|
||||
kitten icat image.jpeg
|
||||
kitten icat image.jpeg
|
||||
|
||||
It supports all image types supported by `ImageMagick
|
||||
@@ -15,7 +15,7 @@ It supports all image types supported by `ImageMagick
|
||||
|
||||
You might want to create an alias in your shell's configuration files::
|
||||
|
||||
alias icat="kitty +kitten icat"
|
||||
alias icat="kitten icat"
|
||||
|
||||
Then you can simply use ``icat image.png`` to view images.
|
||||
|
||||
|
||||
@@ -33,14 +33,14 @@ To try it out, simply run:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
kitty +kitten ssh some-hostname-to-connect-to
|
||||
kitten ssh some-hostname-to-connect-to
|
||||
|
||||
You should end up at a shell prompt on the remote host, with shell integration
|
||||
enabled. If you like it you can add an alias to it in your shell's rc files:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
alias s="kitty +kitten ssh"
|
||||
alias s="kitten ssh"
|
||||
|
||||
So now you can just type ``s hostname`` to connect.
|
||||
|
||||
@@ -79,13 +79,13 @@ Additionally, you can pass config options on the command line:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
kitty +kitten ssh --kitten interpreter=python servername
|
||||
kitten ssh --kitten interpreter=python servername
|
||||
|
||||
The :code:`--kitten` argument can be specified multiple times, with directives
|
||||
from :file:`ssh.conf`. These are merged with :file:`ssh.conf` as if they were
|
||||
appended to the end of that file. They apply only to the host being SSHed to by
|
||||
this invocation, so any :opt:`hostname <kitten-ssh.hostname>` directives are
|
||||
ignored.
|
||||
from :file:`ssh.conf`. These override the final options used for the matched host, as if they
|
||||
had been appended to the end of the matching section for that host in
|
||||
:file:`ssh.conf`. They apply only to the host being SSHed to by this invocation,
|
||||
so any :opt:`hostname <kitten-ssh.hostname>` directives are ignored.
|
||||
|
||||
.. warning::
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ The themes kitten allows you to easily change color themes, from a collection of
|
||||
over three hundred pre-built themes available at `kitty-themes
|
||||
<https://github.com/kovidgoyal/kitty-themes>`_. To use it, simply run::
|
||||
|
||||
kitty +kitten themes
|
||||
kitten themes
|
||||
|
||||
|
||||
.. image:: ../screenshots/themes.png
|
||||
@@ -72,7 +72,7 @@ Changing the theme non-interactively
|
||||
You can specify the theme name as an argument when invoking the kitten to have
|
||||
it change to that theme instantly. For example::
|
||||
|
||||
kitty +kitten themes --reload-in=all Dimmed Monokai
|
||||
kitten themes --reload-in=all Dimmed Monokai
|
||||
|
||||
Will change the theme to ``Dimmed Monokai`` in all running kitty instances. See
|
||||
below for more details on non-interactive operation.
|
||||
|
||||
@@ -15,7 +15,7 @@ clicked. Let us illustrate with some examples, first. Create the file
|
||||
# Open any image in the full kitty window by clicking on it
|
||||
protocol file
|
||||
mime image/*
|
||||
action launch --type=overlay kitty +kitten icat --hold ${FILE_PATH}
|
||||
action launch --type=overlay kitten icat --hold ${FILE_PATH}
|
||||
|
||||
Now, run ``ls --hyperlink=auto`` in kitty and click on the filename of an
|
||||
image, holding down :kbd:`ctrl+shift`. It will be opened over the current
|
||||
|
||||
@@ -266,7 +266,9 @@ Would open the scrollback buffer in a new :term:`window` when you press the
|
||||
:kbd:`F1` key. See :sc:`show_scrollback <show_scrollback>` for details.
|
||||
|
||||
If you want to use it with an editor such as :program:`vim` to get more powerful
|
||||
features, you can see tips for doing so, in :iss:`this thread <719>`.
|
||||
features, see for example, `kitty-scrollback.nvim
|
||||
<https://github.com/mikesmithgh/kitty-scrollback.nvim>`__ or `kitty-grab <https://github.com/yurikhan/kitty_grab>`__
|
||||
or see more tips for using various editor programs, in :iss:`this thread <719>`.
|
||||
|
||||
If you wish to store very large amounts of scrollback to view using the piping
|
||||
or :sc:`show_scrollback <show_scrollback>` features, you can use the
|
||||
|
||||
@@ -91,6 +91,10 @@ no-complete
|
||||
Note that for the fish shell this does not take effect, since fish already
|
||||
comes with a kitty completion script.
|
||||
|
||||
no-sudo
|
||||
Do not alias :program:`sudo` to ensure the kitty terminfo files are
|
||||
available in the sudo environment
|
||||
|
||||
|
||||
More ways to browse command output
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -214,7 +218,7 @@ Shell integration over SSH
|
||||
The easiest way to have shell integration work when SSHing into remote systems
|
||||
is to use the :doc:`ssh kitten <kittens/ssh>`. Simply run::
|
||||
|
||||
kitty +kitten ssh hostname
|
||||
kitten ssh hostname
|
||||
|
||||
And, by magic, you will be logged into the remote system with fully functional
|
||||
shell integration. Alternately, you can :ref:`setup shell integration manually
|
||||
|
||||
@@ -113,7 +113,7 @@ def generate(
|
||||
sz = screen->parser_buf_pos - pos;
|
||||
g.payload_sz = sizeof(payload);
|
||||
if (!base64_decode32(screen->parser_buf + pos, sz, payload, &g.payload_sz)) {{
|
||||
REPORT_ERROR("Failed to parse {command_class} command payload with error: %s", "output buffer for base64_decode too small"); return; }}
|
||||
REPORT_ERROR("Failed to parse {command_class} command payload with error: payload size (%zu) too large", sz); return; }}
|
||||
pos = screen->parser_buf_pos;
|
||||
}}
|
||||
break;
|
||||
|
||||
@@ -308,6 +308,10 @@ static NSDictionary<NSString*,NSNumber*> *global_shortcuts = nil;
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
|
||||
return YES;
|
||||
}
|
||||
|
||||
static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
||||
|
||||
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag
|
||||
|
||||
4
glfw/dbus_glfw.c
vendored
4
glfw/dbus_glfw.c
vendored
@@ -33,12 +33,12 @@
|
||||
|
||||
static void
|
||||
report_error(DBusError *err, const char *fmt, ...) {
|
||||
static char buf[1024];
|
||||
static char buf[4096];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int n = vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
snprintf(buf + n, sizeof(buf), ". DBUS error: %s", err->message);
|
||||
if (n >= 0 && (size_t)n < (sizeof(buf) - 256)) snprintf(buf + n, sizeof(buf) - n, ". DBUS error: %s", err->message ? err->message : "(null)");
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", buf);
|
||||
dbus_error_free(err);
|
||||
}
|
||||
|
||||
18
go.mod
18
go.mod
@@ -3,19 +3,19 @@ module kitty
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924
|
||||
github.com/alecthomas/chroma/v2 v2.8.0
|
||||
github.com/ALTree/bigfloat v0.2.0
|
||||
github.com/alecthomas/chroma/v2 v2.9.1
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dlclark/regexp2 v1.10.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/seancfoley/ipaddress-go v1.5.4
|
||||
github.com/shirou/gopsutil/v3 v3.23.7
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/seancfoley/ipaddress-go v1.5.5
|
||||
github.com/shirou/gopsutil/v3 v3.23.9
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
|
||||
golang.org/x/image v0.11.0
|
||||
golang.org/x/sys v0.11.0
|
||||
golang.org/x/image v0.12.0
|
||||
golang.org/x/sys v0.12.0
|
||||
howett.net/plist v1.0.0
|
||||
)
|
||||
|
||||
@@ -24,9 +24,9 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||
github.com/seancfoley/bintree v1.2.1 // indirect
|
||||
github.com/seancfoley/bintree v1.2.3 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
)
|
||||
|
||||
40
go.sum
40
go.sum
@@ -1,9 +1,9 @@
|
||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924 h1:DG4UyTVIujioxwJc8Zj8Nabz1L1wTgQ/xNBSQDfdP3I=
|
||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
|
||||
github.com/ALTree/bigfloat v0.2.0 h1:AwNzawrpFuw55/YDVlcPw0F0cmmXrmngBHhVrvdXPvM=
|
||||
github.com/ALTree/bigfloat v0.2.0/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
|
||||
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
||||
github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
||||
github.com/alecthomas/chroma/v2 v2.8.0 h1:w9WJUjFFmHHB2e8mRpL9jjy3alYDlU0QLDezj1xE264=
|
||||
github.com/alecthomas/chroma/v2 v2.8.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
|
||||
github.com/alecthomas/chroma/v2 v2.9.1 h1:0O3lTQh9FxazJ4BYE/MOi/vDGuHn7B+6Bu902N2UZvU=
|
||||
github.com/alecthomas/chroma/v2 v2.9.1/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc=
|
||||
@@ -20,8 +20,8 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -35,12 +35,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/seancfoley/bintree v1.2.1 h1:Z/iNjRKkXnn0CTW7jDQYtjW5fz2GH1yWvOTJ4MrMvdo=
|
||||
github.com/seancfoley/bintree v1.2.1/go.mod h1:hIUabL8OFYyFVTQ6azeajbopogQc2l5C/hiXMcemWNU=
|
||||
github.com/seancfoley/ipaddress-go v1.5.4 h1:ZdjewWC1J2y5ruQjWHwK6rA1tInWB6mz1ftz6uTm+Uw=
|
||||
github.com/seancfoley/ipaddress-go v1.5.4/go.mod h1:fpvVPC+Jso+YEhNcNiww8HQmBgKP8T4T6BTp1SLxxIo=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||
github.com/seancfoley/bintree v1.2.3 h1:6SPPax/9Dilcs3mDTj3CarRCWPZJV30KyP3cjcEwF70=
|
||||
github.com/seancfoley/bintree v1.2.3/go.mod h1:hIUabL8OFYyFVTQ6azeajbopogQc2l5C/hiXMcemWNU=
|
||||
github.com/seancfoley/ipaddress-go v1.5.5 h1:Q2isCacDQ3A46hxSbM9Q2+Gs4IopCVz1oH88L5eEgP4=
|
||||
github.com/seancfoley/ipaddress-go v1.5.5/go.mod h1:C+VHKCmxTfYODkkItOj9lMemZLMigwo28E+ARlPqpk0=
|
||||
github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E=
|
||||
github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
@@ -52,9 +52,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
@@ -69,8 +68,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
|
||||
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -87,12 +86,11 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -100,7 +98,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
||||
@@ -60,22 +60,22 @@ the clipboard. Some examples:
|
||||
.. code:: sh
|
||||
|
||||
# Copy an image to the clipboard:
|
||||
kitty +kitten clipboard picture.png
|
||||
kitten clipboard picture.png
|
||||
|
||||
# Copy an image and some text to the clipboard:
|
||||
kitty +kitten clipboard picture.jpg text.txt
|
||||
kitten clipboard picture.jpg text.txt
|
||||
|
||||
# Copy text from STDIN and an image to the clipboard:
|
||||
echo hello | kitty +kitten clipboard picture.png /dev/stdin
|
||||
echo hello | kitten clipboard picture.png /dev/stdin
|
||||
|
||||
# Copy any raster image available on the clipboard to a PNG file:
|
||||
kitty +kitten clipboard -g picture.png
|
||||
kitten clipboard -g picture.png
|
||||
|
||||
# Copy an image to a file and text to STDOUT:
|
||||
kitty +kitten clipboard -g picture.png /dev/stdout
|
||||
kitten clipboard -g picture.png /dev/stdout
|
||||
|
||||
# List the formats available on the system clipboard
|
||||
kitty +kitten clipboard -g -m . /dev/stdout
|
||||
kitten clipboard -g -m . /dev/stdout
|
||||
'''
|
||||
|
||||
usage = '[files to copy to/from]'
|
||||
|
||||
@@ -263,6 +263,9 @@ func walk(base string, patterns []string, names *utils.Set[string], pmap, path_n
|
||||
return err
|
||||
}
|
||||
return filepath.WalkDir(base, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
is_allowed := allowed(path, patterns...)
|
||||
if !is_allowed {
|
||||
if d.IsDir() {
|
||||
@@ -297,7 +300,9 @@ func (self *Collection) collect_files(left, right string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = walk(right, conf.Ignore_name, right_names, right_path_map, path_name_map)
|
||||
if err = walk(right, conf.Ignore_name, right_names, right_path_map, path_name_map); err != nil {
|
||||
return err
|
||||
}
|
||||
common_names := left_names.Intersect(right_names)
|
||||
changed_names := utils.NewSet[string](common_names.Len())
|
||||
for n := range common_names.Iterable() {
|
||||
|
||||
@@ -19,16 +19,16 @@ var _ = fmt.Print
|
||||
func TestDiffCollectWalk(t *testing.T) {
|
||||
tdir := t.TempDir()
|
||||
j := func(x ...string) string { return filepath.Join(append([]string{tdir}, x...)...) }
|
||||
os.MkdirAll(j("a", "b"), 0o700)
|
||||
os.WriteFile(j("a/b/c"), nil, 0o600)
|
||||
os.WriteFile(j("b"), nil, 0o600)
|
||||
os.WriteFile(j("d"), nil, 0o600)
|
||||
os.WriteFile(j("e"), nil, 0o600)
|
||||
os.WriteFile(j("#d#"), nil, 0o600)
|
||||
os.WriteFile(j("e~"), nil, 0o600)
|
||||
os.MkdirAll(j("f"), 0o700)
|
||||
os.WriteFile(j("f/g"), nil, 0o600)
|
||||
os.WriteFile(j("h space"), nil, 0o600)
|
||||
_ = os.MkdirAll(j("a", "b"), 0o700)
|
||||
_ = os.WriteFile(j("a/b/c"), nil, 0o600)
|
||||
_ = os.WriteFile(j("b"), nil, 0o600)
|
||||
_ = os.WriteFile(j("d"), nil, 0o600)
|
||||
_ = os.WriteFile(j("e"), nil, 0o600)
|
||||
_ = os.WriteFile(j("#d#"), nil, 0o600)
|
||||
_ = os.WriteFile(j("e~"), nil, 0o600)
|
||||
_ = os.MkdirAll(j("f"), 0o700)
|
||||
_ = os.WriteFile(j("f/g"), nil, 0o600)
|
||||
_ = os.WriteFile(j("h space"), nil, 0o600)
|
||||
|
||||
expected_names := utils.NewSetWithItems("d", "e", "f/g", "h space")
|
||||
expected_pmap := map[string]string{
|
||||
|
||||
@@ -81,23 +81,37 @@ func clear_background(style *chroma.Style) *chroma.Style {
|
||||
return style
|
||||
}
|
||||
|
||||
func ansi_formatter(w io.Writer, style *chroma.Style, it chroma.Iterator) error {
|
||||
func ansi_formatter(w io.Writer, style *chroma.Style, it chroma.Iterator) (err error) {
|
||||
const SGR_PREFIX = "\033["
|
||||
const SGR_SUFFIX = "m"
|
||||
style = clear_background(style)
|
||||
before, after := make([]byte, 0, 64), make([]byte, 0, 64)
|
||||
nl := []byte{'\n'}
|
||||
write_sgr := func(which []byte) {
|
||||
write_sgr := func(which []byte) (err error) {
|
||||
if len(which) > 1 {
|
||||
w.Write(utils.UnsafeStringToBytes(SGR_PREFIX))
|
||||
w.Write(which[:len(which)-1])
|
||||
w.Write(utils.UnsafeStringToBytes(SGR_SUFFIX))
|
||||
if _, err = w.Write(utils.UnsafeStringToBytes(SGR_PREFIX)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = w.Write(which[:len(which)-1]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = w.Write(utils.UnsafeStringToBytes(SGR_SUFFIX)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
write := func(text string) {
|
||||
write_sgr(before)
|
||||
w.Write(utils.UnsafeStringToBytes(text))
|
||||
write_sgr(after)
|
||||
write := func(text string) (err error) {
|
||||
if err = write_sgr(before); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = w.Write(utils.UnsafeStringToBytes(text)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = write_sgr(after); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for token := it(); token != chroma.EOF; token = it() {
|
||||
@@ -127,11 +141,17 @@ func ansi_formatter(w io.Writer, style *chroma.Style, it chroma.Iterator) error
|
||||
for text != "" {
|
||||
idx := strings.IndexByte(text, '\n')
|
||||
if idx < 0 {
|
||||
write(text)
|
||||
if err = write(text); err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
write(text[:idx])
|
||||
w.Write(nl)
|
||||
if err = write(text[:idx]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = w.Write(nl); err != nil {
|
||||
return err
|
||||
}
|
||||
text = text[idx+1:]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,13 +76,15 @@ func get_ssh_file(hostname, rpath string) (string, error) {
|
||||
}
|
||||
ans := filepath.Join(tdir, rpath)
|
||||
if count == 1 {
|
||||
filepath.WalkDir(tdir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err = filepath.WalkDir(tdir, func(path string, d fs.DirEntry, err error) error {
|
||||
if !d.IsDir() {
|
||||
ans = path
|
||||
return fs.SkipAll
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return ans, nil
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func read_relevant_kitty_opts(path string) KittyOpts {
|
||||
return nil
|
||||
}
|
||||
cp := config.ConfigParser{LineHandler: handle_line}
|
||||
cp.ParseFiles(path)
|
||||
_ = cp.ParseFiles(path)
|
||||
return ans
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func (self *Handler) handle_wheel_event(up bool) {
|
||||
if up {
|
||||
amt *= -1
|
||||
}
|
||||
self.dispatch_action(`scroll_by`, strconv.Itoa(amt))
|
||||
_ = self.dispatch_action(`scroll_by`, strconv.Itoa(amt))
|
||||
}
|
||||
|
||||
type line_pos struct {
|
||||
@@ -113,6 +113,7 @@ func (self *Handler) drag_scroll_tick(timer_id loop.IdType) error {
|
||||
}
|
||||
|
||||
var debugprintln = tty.DebugPrintln
|
||||
var _ = debugprintln
|
||||
|
||||
func (self *Handler) update_mouse_selection(ev *loop.MouseEvent) {
|
||||
if !self.mouse_selection.IsActive() {
|
||||
|
||||
@@ -159,7 +159,7 @@ var format_as_sgr struct {
|
||||
title, margin, added, removed, added_margin, removed_margin, filler, margin_filler, hunk_margin, hunk, selection, search string
|
||||
}
|
||||
|
||||
var statusline_format, added_count_format, removed_count_format, message_format, selection_format func(...any) string
|
||||
var statusline_format, added_count_format, removed_count_format, message_format func(...any) string
|
||||
|
||||
func create_formatters() {
|
||||
ctx := style.Context{AllowEscapeCodes: true}
|
||||
@@ -169,7 +169,6 @@ func create_formatters() {
|
||||
return ans
|
||||
}
|
||||
format_as_sgr.filler = only_open("bg=" + conf.Filler_bg.AsRGBSharp())
|
||||
debugprintln(11111, conf.Margin_filler_bg.IsSet)
|
||||
if conf.Margin_filler_bg.IsSet {
|
||||
format_as_sgr.margin_filler = only_open("bg=" + conf.Margin_filler_bg.Color.AsRGBSharp())
|
||||
} else {
|
||||
@@ -392,8 +391,6 @@ func image_lines(left_path, right_path string, screen_size screen_size, margin_s
|
||||
return append(ans, ll), nil
|
||||
}
|
||||
|
||||
type formatter = func(...any) string
|
||||
|
||||
func first_binary_line(left_path, right_path string, columns, margin_size int, renderer func(path string) (string, error)) (*LogicalLine, error) {
|
||||
available_cols := columns/2 - margin_size
|
||||
ll := LogicalLine{
|
||||
@@ -771,10 +768,3 @@ func render(collection *Collection, diff_map map[string]*Patch, screen_size scre
|
||||
}
|
||||
return &LogicalLines{lines: ll, margin_size: margin_size, columns: columns}, err
|
||||
}
|
||||
|
||||
func (self *LogicalLines) num_of_screen_lines() (ans int) {
|
||||
for _, l := range self.lines {
|
||||
ans += len(l.screen_lines)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ func (self *Handler) initialize() {
|
||||
func (self *Handler) generate_diff() {
|
||||
self.diff_map = nil
|
||||
jobs := make([]diff_job, 0, 32)
|
||||
self.collection.Apply(func(path, typ, changed_path string) error {
|
||||
_ = self.collection.Apply(func(path, typ, changed_path string) error {
|
||||
if typ == "diff" {
|
||||
if is_path_text(path) && is_path_text(changed_path) {
|
||||
jobs = append(jobs, diff_job{path, changed_path})
|
||||
@@ -188,7 +188,7 @@ func (self *Handler) highlight_all() {
|
||||
}
|
||||
|
||||
func (self *Handler) load_all_images() {
|
||||
self.collection.Apply(func(path, item_type, changed_path string) error {
|
||||
_ = self.collection.Apply(func(path, item_type, changed_path string) error {
|
||||
if path != "" && is_image(path) {
|
||||
image_collection.AddPaths(path)
|
||||
self.image_count++
|
||||
@@ -627,11 +627,9 @@ func (self *Handler) dispatch_action(name, args string) error {
|
||||
}
|
||||
done = self.scroll_lines(amt) != 0
|
||||
default:
|
||||
npos := self.scroll_pos
|
||||
npos := ScrollPos{}
|
||||
if strings.Contains(args, `end`) {
|
||||
npos = self.max_scroll_pos
|
||||
} else {
|
||||
npos = ScrollPos{}
|
||||
}
|
||||
done = npos != self.scroll_pos
|
||||
self.scroll_pos = npos
|
||||
|
||||
@@ -134,7 +134,7 @@ window, :code:`window` a new kitty window, :code:`tab` a new tab,
|
||||
:code:`os_window` a new OS window and :code:`background` run in the background.
|
||||
The actual action is whatever arguments are provided to the kitten, for
|
||||
example:
|
||||
:code:`kitty +kitten hints --type=linenum --linenum-action=tab vim +{line} {path}`
|
||||
:code:`kitten hints --type=linenum --linenum-action=tab vim +{line} {path}`
|
||||
will open the matched path at the matched line number in vim in
|
||||
a new kitty tab. Note that in order to use :option:`--program` to copy or paste
|
||||
the provided arguments, you need to use the special value :code:`self`.
|
||||
|
||||
@@ -286,6 +286,9 @@ func (self *stdout_filter) Write(p []byte) (n int, err error) {
|
||||
|
||||
func main(_ *cli.Command, _ *Options, args []string) (rc int, err error) {
|
||||
delegate_to_rg, sanitized_args, kitten_opts, err := parse_args(args...)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
if delegate_to_rg {
|
||||
sanitized_args = append([]string{"rg"}, sanitized_args...)
|
||||
err = unix.Exec(RgExe(), sanitized_args, os.Environ())
|
||||
|
||||
@@ -38,14 +38,14 @@ func DetectSupport(timeout time.Duration) (memory, files, direct bool, err error
|
||||
}
|
||||
if len(shm_files_to_delete) > 0 && transfer_by_memory != supported {
|
||||
for _, name := range shm_files_to_delete {
|
||||
name.Unlink()
|
||||
_ = name.Unlink()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
lp.OnInitialize = func() (string, error) {
|
||||
var iid uint32
|
||||
lp.AddTimer(timeout, false, func(loop.IdType) error {
|
||||
_, _ = lp.AddTimer(timeout, false, func(loop.IdType) error {
|
||||
return fmt.Errorf("Timed out waiting for a response form the terminal: %w", os.ErrDeadlineExceeded)
|
||||
})
|
||||
|
||||
@@ -54,7 +54,7 @@ func DetectSupport(timeout time.Duration) (memory, files, direct bool, err error
|
||||
g1 := &graphics.GraphicsCommand{}
|
||||
g1.SetTransmission(t).SetAction(graphics.GRT_action_query).SetImageId(iid).SetDataWidth(1).SetDataHeight(1).SetFormat(
|
||||
graphics.GRT_format_rgb).SetDataSize(uint64(len(payload)))
|
||||
g1.WriteWithPayloadToLoop(lp, utils.UnsafeStringToBytes(payload))
|
||||
_ = g1.WriteWithPayloadToLoop(lp, utils.UnsafeStringToBytes(payload))
|
||||
return iid
|
||||
}
|
||||
|
||||
@@ -63,7 +63,9 @@ func DetectSupport(timeout time.Duration) (memory, files, direct bool, err error
|
||||
if err == nil {
|
||||
file_query_id = g(graphics.GRT_transmission_tempfile, tf.Name())
|
||||
temp_files_to_delete = append(temp_files_to_delete, tf.Name())
|
||||
tf.Write([]byte{1, 2, 3})
|
||||
if _, err = tf.Write([]byte{1, 2, 3}); err != nil {
|
||||
print_error("Failed to write to temporary file for data transfer, file based transfer is disabled. Error: %v", err)
|
||||
}
|
||||
tf.Close()
|
||||
} else {
|
||||
print_error("Failed to create temporary file for data transfer, file based transfer is disabled. Error: %v", err)
|
||||
|
||||
@@ -42,7 +42,7 @@ const (
|
||||
supported
|
||||
)
|
||||
|
||||
var transfer_by_file, transfer_by_memory, transfer_by_stream transfer_mode
|
||||
var transfer_by_file, transfer_by_memory transfer_mode
|
||||
|
||||
var files_channel chan input_arg
|
||||
var output_channel chan *image_data
|
||||
@@ -146,11 +146,15 @@ func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) {
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
t, err := tty.OpenControllingTerm()
|
||||
if err != nil {
|
||||
return 1, fmt.Errorf("Failed to open controlling terminal with error: %w", err)
|
||||
if tty.IsTerminal(os.Stdout.Fd()) {
|
||||
screen_size, err = tty.GetSize(int(os.Stdout.Fd()))
|
||||
} else {
|
||||
t, oerr := tty.OpenControllingTerm()
|
||||
if oerr != nil {
|
||||
return 1, fmt.Errorf("Failed to open controlling terminal with error: %w", oerr)
|
||||
}
|
||||
screen_size, err = t.GetSize()
|
||||
}
|
||||
screen_size, err = t.GetSize()
|
||||
if err != nil {
|
||||
return 1, fmt.Errorf("Failed to query terminal using TIOCGWINSZ with error: %w", err)
|
||||
}
|
||||
@@ -162,7 +166,9 @@ func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) {
|
||||
if opts.Clear {
|
||||
cc := &graphics.GraphicsCommand{}
|
||||
cc.SetAction(graphics.GRT_action_delete).SetDelete(graphics.GRT_free_visible)
|
||||
cc.WriteWithPayloadTo(os.Stdout, nil)
|
||||
if err = cc.WriteWithPayloadTo(os.Stdout, nil); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
}
|
||||
if screen_size.Xpixel == 0 || screen_size.Ypixel == 0 {
|
||||
return 1, fmt.Errorf("Terminal does not support reporting screen sizes in pixels, use a terminal such as kitty, WezTerm, Konsole, etc. that does.")
|
||||
@@ -224,7 +230,6 @@ func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) {
|
||||
// tmux doesn't allow responses from the terminal so we can't detect if memory or file based transferring is supported
|
||||
transfer_by_memory = unsupported
|
||||
transfer_by_file = unsupported
|
||||
transfer_by_stream = supported
|
||||
}
|
||||
if opts.DetectSupport {
|
||||
if transfer_by_memory == supported {
|
||||
|
||||
@@ -75,7 +75,7 @@ detecting image display support.
|
||||
--print-window-size
|
||||
type=bool-set
|
||||
Print out the window size as <:italic:`width`>x<:italic:`height`> (in pixels) and quit. This is a
|
||||
convenience method to query the window size if using :code:`kitty +kitten icat`
|
||||
convenience method to query the window size if using :code:`kitten icat`
|
||||
from a scripting language that cannot make termios calls.
|
||||
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ func load_one_frame_image(ctx *images.Context, imgd *image_data, src *opened_inp
|
||||
}
|
||||
|
||||
var debugprintln = tty.DebugPrintln
|
||||
var _ = debugprintln
|
||||
|
||||
func (frame *image_frame) set_disposal(anchor_frame int, disposal byte) int {
|
||||
anchor_frame, frame.compose_onto = images.SetGIFFrameDisposal(frame.number, anchor_frame, disposal)
|
||||
|
||||
@@ -93,7 +93,7 @@ func process_dirs(args ...string) (results []input_arg, err error) {
|
||||
return nil, &fs.PathError{Op: "Stat", Path: arg, Err: err}
|
||||
}
|
||||
if s.IsDir() {
|
||||
filepath.WalkDir(arg, func(path string, d fs.DirEntry, walk_err error) error {
|
||||
if err = filepath.WalkDir(arg, func(path string, d fs.DirEntry, walk_err error) error {
|
||||
if walk_err != nil {
|
||||
if d == nil {
|
||||
err = &fs.PathError{Op: "Stat", Path: arg, Err: walk_err}
|
||||
@@ -107,7 +107,9 @@ func process_dirs(args ...string) (results []input_arg, err error) {
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
results = append(results, input_arg{arg: arg, value: arg})
|
||||
}
|
||||
@@ -124,7 +126,7 @@ type opened_input struct {
|
||||
|
||||
func (self *opened_input) Rewind() {
|
||||
if self.file != nil {
|
||||
self.file.Seek(0, io.SeekStart)
|
||||
_, _ = self.file.Seek(0, io.SeekStart)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ func transmit_shm(imgd *image_data, frame_num int, frame *image_frame) (err erro
|
||||
}
|
||||
defer f.Close()
|
||||
data_size, _ = f.Seek(0, io.SeekEnd)
|
||||
f.Seek(0, io.SeekStart)
|
||||
_, _ = f.Seek(0, io.SeekStart)
|
||||
mmap, err = shm.CreateTemp("icat-*", uint64(data_size))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create a SHM file for transmission: %w", err)
|
||||
@@ -108,7 +108,7 @@ func transmit_shm(imgd *image_data, frame_num int, frame *image_frame) (err erro
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
mmap.Unlink()
|
||||
_ = mmap.Unlink()
|
||||
return fmt.Errorf("Failed to read data from image output data file: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -128,10 +128,10 @@ func transmit_shm(imgd *image_data, frame_num int, frame *image_frame) (err erro
|
||||
gc := gc_for_image(imgd, frame_num, frame)
|
||||
gc.SetTransmission(graphics.GRT_transmission_sharedmem)
|
||||
gc.SetDataSize(uint64(data_size))
|
||||
gc.WriteWithPayloadTo(os.Stdout, utils.UnsafeStringToBytes(mmap.Name()))
|
||||
err = gc.WriteWithPayloadTo(os.Stdout, utils.UnsafeStringToBytes(mmap.Name()))
|
||||
mmap.Close()
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
func transmit_file(imgd *image_data, frame_num int, frame *image_frame) (err error) {
|
||||
@@ -174,8 +174,7 @@ func transmit_file(imgd *image_data, frame_num int, frame *image_frame) (err err
|
||||
if data_size > 0 {
|
||||
gc.SetDataSize(uint64(data_size))
|
||||
}
|
||||
gc.WriteWithPayloadTo(os.Stdout, utils.UnsafeStringToBytes(fname))
|
||||
return nil
|
||||
return gc.WriteWithPayloadTo(os.Stdout, utils.UnsafeStringToBytes(fname))
|
||||
}
|
||||
|
||||
func transmit_stream(imgd *image_data, frame_num int, frame *image_frame) (err error) {
|
||||
@@ -192,8 +191,7 @@ func transmit_stream(imgd *image_data, frame_num int, frame *image_frame) (err e
|
||||
}
|
||||
}
|
||||
gc := gc_for_image(imgd, frame_num, frame)
|
||||
gc.WriteWithPayloadTo(os.Stdout, data)
|
||||
return nil
|
||||
return gc.WriteWithPayloadTo(os.Stdout, data)
|
||||
}
|
||||
|
||||
func calculate_in_cell_x_offset(width, cell_width int) int {
|
||||
@@ -291,7 +289,7 @@ func transmit_image(imgd *image_data) {
|
||||
frame.filename = ""
|
||||
}
|
||||
if frame.shm != nil {
|
||||
frame.shm.Unlink()
|
||||
_ = frame.shm.Unlink()
|
||||
frame.shm.Close()
|
||||
frame.shm = nil
|
||||
}
|
||||
@@ -386,11 +384,15 @@ func transmit_image(imgd *image_data) {
|
||||
case opts.Loop > 0:
|
||||
c.SetNumberOfLoops(uint64(opts.Loop) + 1)
|
||||
}
|
||||
c.WriteWithPayloadTo(os.Stdout, nil)
|
||||
if imgd.err = c.WriteWithPayloadTo(os.Stdout, nil); imgd.err != nil {
|
||||
return
|
||||
}
|
||||
case 1:
|
||||
c := frame_control_cmd
|
||||
c.SetAnimationControl(2) // set animation to loading mode
|
||||
c.WriteWithPayloadTo(os.Stdout, nil)
|
||||
if imgd.err = c.WriteWithPayloadTo(os.Stdout, nil); imgd.err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,7 +402,9 @@ func transmit_image(imgd *image_data) {
|
||||
if is_animated {
|
||||
c := frame_control_cmd
|
||||
c.SetAnimationControl(3) // set animation to normal mode
|
||||
c.WriteWithPayloadTo(os.Stdout, nil)
|
||||
if imgd.err = c.WriteWithPayloadTo(os.Stdout, nil); imgd.err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if imgd.move_to.x == 0 {
|
||||
fmt.Println() // ensure cursor is on new line
|
||||
|
||||
@@ -57,12 +57,13 @@ func RunSSHAskpass() {
|
||||
fatal(fmt.Errorf("Failed to create SHM file with error: %w", err))
|
||||
}
|
||||
defer data_shm.Close()
|
||||
defer data_shm.Unlink()
|
||||
defer func() { _ = data_shm.Unlink() }()
|
||||
|
||||
data_shm.Slice()[0] = 0
|
||||
shm.WriteWithSize(data_shm, data, 1)
|
||||
err = data_shm.Flush()
|
||||
if err != nil {
|
||||
if err = shm.WriteWithSize(data_shm, data, 1); err != nil {
|
||||
fatal(fmt.Errorf("Failed to write to SHM file with error: %w", err))
|
||||
}
|
||||
if err = data_shm.Flush(); err != nil {
|
||||
fatal(fmt.Errorf("Failed to flush SHM file with error: %w", err))
|
||||
}
|
||||
trigger_ask(data_shm.Name())
|
||||
|
||||
@@ -169,7 +169,7 @@ func get_arcname(loc, dest, home string) (arcname string) {
|
||||
arcname = dest
|
||||
} else {
|
||||
arcname = filepath.Clean(loc)
|
||||
if filepath.HasPrefix(arcname, home) {
|
||||
if strings.HasPrefix(arcname, home) {
|
||||
ra, err := filepath.Rel(home, arcname)
|
||||
if err == nil {
|
||||
arcname = ra
|
||||
@@ -391,9 +391,20 @@ func (self *ConfigSet) line_handler(key, val string) error {
|
||||
func load_config(hostname_to_match string, username_to_match string, overrides []string, paths ...string) (*Config, []config.ConfigLine, error) {
|
||||
ans := &ConfigSet{all_configs: []*Config{NewConfig()}}
|
||||
p := config.ConfigParser{LineHandler: ans.line_handler}
|
||||
err := p.LoadConfig("ssh.conf", paths, overrides)
|
||||
err := p.LoadConfig("ssh.conf", paths, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return config_for_hostname(hostname_to_match, username_to_match, ans), p.BadLines(), nil
|
||||
final_conf := config_for_hostname(hostname_to_match, username_to_match, ans)
|
||||
bad_lines := p.BadLines()
|
||||
if len(overrides) > 0 {
|
||||
h := final_conf.Hostname
|
||||
override_parser := config.ConfigParser{LineHandler: final_conf.Parse}
|
||||
if err = override_parser.ParseOverrides(overrides...); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
bad_lines = append(bad_lines, override_parser.BadLines()...)
|
||||
final_conf.Hostname = h
|
||||
}
|
||||
return final_conf, bad_lines, nil
|
||||
}
|
||||
|
||||
@@ -24,11 +24,14 @@ func TestSSHConfigParsing(t *testing.T) {
|
||||
hostname := "unmatched"
|
||||
username := ""
|
||||
conf := ""
|
||||
overrides := []string{}
|
||||
for_python := false
|
||||
cf := filepath.Join(tdir, "ssh.conf")
|
||||
rt := func(expected_env ...string) {
|
||||
os.WriteFile(cf, []byte(conf), 0o600)
|
||||
c, bad_lines, err := load_config(hostname, username, nil, cf)
|
||||
if err := os.WriteFile(cf, []byte(conf), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, bad_lines, err := load_config(hostname, username, overrides, cf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -52,6 +55,10 @@ func TestSSHConfigParsing(t *testing.T) {
|
||||
rt()
|
||||
conf = "env a=b"
|
||||
rt(`export 'a'="b"`)
|
||||
conf = "env a=b"
|
||||
overrides = []string{"env=c=d"}
|
||||
rt(`export 'a'="b"`, `export 'c'="d"`)
|
||||
overrides = nil
|
||||
|
||||
conf = "env a=\\"
|
||||
rt(`export 'a'="\\"`)
|
||||
|
||||
@@ -65,7 +65,6 @@ func get_destination(hostname string) (username, hostname_for_match string) {
|
||||
}
|
||||
if !parsed && strings.Contains(hostname, "@") && hostname[0] != '@' {
|
||||
_, hostname_for_match, _ = strings.Cut(hostname, "@")
|
||||
parsed = true
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -116,9 +115,6 @@ func parse_kitten_args(found_extra_args []string, username, hostname_for_match s
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(overrides) > 0 {
|
||||
overrides = append([]string{"hostname " + username + "@" + hostname_for_match}, overrides...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -155,7 +151,7 @@ func set_askpass() (need_to_request_data bool) {
|
||||
sentinel_exists := err == nil
|
||||
if sentinel_exists || GetSSHVersion().SupportsAskpassRequire() {
|
||||
if !sentinel_exists {
|
||||
os.WriteFile(sentinel, []byte{0}, 0o644)
|
||||
_ = os.WriteFile(sentinel, []byte{0}, 0o644)
|
||||
}
|
||||
need_to_request_data = false
|
||||
}
|
||||
@@ -322,9 +318,13 @@ func make_tarfile(cd *connection_data, get_local_env func(string) (string, bool)
|
||||
return nil
|
||||
|
||||
}
|
||||
add_data(fe{"data.sh", utils.UnsafeStringToBytes(env_script)})
|
||||
if err = add_data(fe{"data.sh", utils.UnsafeStringToBytes(env_script)}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cd.script_type == "sh" {
|
||||
add_data(fe{"bootstrap-utils.sh", shell_integration.Data()[path.Join("shell-integration/ssh/bootstrap-utils.sh")].Data})
|
||||
if err = add_data(fe{"bootstrap-utils.sh", shell_integration.Data()[path.Join("shell-integration/ssh/bootstrap-utils.sh")].Data}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if ksi != "" {
|
||||
for _, fname := range shell_integration.Data().FilesMatching(
|
||||
@@ -547,7 +547,7 @@ func drain_potential_tty_garbage(term *tty.Term) {
|
||||
buf := make([]byte, 0, 8192)
|
||||
for !bytes.Contains(data, q) {
|
||||
buf = buf[:cap(buf)]
|
||||
timeout := give_up_at.Sub(time.Now())
|
||||
timeout := time.Until(give_up_at)
|
||||
if timeout < 0 {
|
||||
break
|
||||
}
|
||||
@@ -597,7 +597,7 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
|
||||
defer func() {
|
||||
if data_shm != nil {
|
||||
data_shm.Close()
|
||||
data_shm.Unlink()
|
||||
_ = data_shm.Unlink()
|
||||
}
|
||||
}()
|
||||
cmd := append([]string{SSHExe()}, ssh_args...)
|
||||
@@ -703,9 +703,9 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
|
||||
c.Stdout = &b
|
||||
c.Stderr = os.Stderr
|
||||
if err := c.Run(); err != nil {
|
||||
return 1, fmt.Errorf("%s\nSetup of port forward in SSH ControlMaster failed with error: %w", string(b.Bytes()), err)
|
||||
return 1, fmt.Errorf("%s\nSetup of port forward in SSH ControlMaster failed with error: %w", b.String(), err)
|
||||
}
|
||||
port, err := strconv.Atoi(strings.TrimSpace(string(b.Bytes())))
|
||||
port, err := strconv.Atoi(strings.TrimSpace(b.String()))
|
||||
if err != nil {
|
||||
os.Stderr.Write(b.Bytes())
|
||||
return 1, fmt.Errorf("Setup of port forward in SSH ControlMaster failed with error: invalid resolved port returned: %s", b.String())
|
||||
@@ -732,7 +732,7 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
|
||||
restore_escape_codes += "\x1b[#Q"
|
||||
}
|
||||
defer func() {
|
||||
term.WriteAllString(restore_escape_codes)
|
||||
_ = term.WriteAllString(restore_escape_codes)
|
||||
term.RestoreAndClose()
|
||||
}()
|
||||
err = get_remote_command(&cd)
|
||||
@@ -760,13 +760,13 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
c.Process.Kill()
|
||||
c.Wait()
|
||||
_ = c.Process.Kill()
|
||||
_ = c.Wait()
|
||||
return 1, err
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
_ = <-sigs
|
||||
<-sigs
|
||||
// ignore any interrupt and terminate signals as they will usually be sent to the ssh child process as well
|
||||
// and we are waiting on that.
|
||||
}()
|
||||
@@ -777,7 +777,7 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
|
||||
var exit_err *exec.ExitError
|
||||
if errors.As(err, &exit_err) {
|
||||
if state := exit_err.ProcessState.String(); state == "signal: interrupt" {
|
||||
unix.Kill(os.Getpid(), unix.SIGINT)
|
||||
_ = unix.Kill(os.Getpid(), unix.SIGINT)
|
||||
// Give the signal time to be delivered
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
|
||||
@@ -111,12 +111,12 @@ func parse_theme_metadata() error {
|
||||
for _, path := range paths {
|
||||
if path != "" {
|
||||
metadata, _, err := themes.ParseThemeMetadata(path)
|
||||
if metadata.Name == "" {
|
||||
metadata.Name = themes.ThemeNameFromFileName(filepath.Base(path))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if metadata.Name == "" {
|
||||
metadata.Name = themes.ThemeNameFromFileName(filepath.Base(path))
|
||||
}
|
||||
ans = append(ans, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,10 +447,10 @@ func (self *handler) draw_theme_demo() {
|
||||
if intense {
|
||||
s = "bright-" + s
|
||||
}
|
||||
if len(c) > trunc {
|
||||
c = c[:trunc]
|
||||
if len(s) > trunc {
|
||||
s = s[:trunc]
|
||||
}
|
||||
buf.WriteString(self.lp.SprintStyled("fg="+c, c))
|
||||
buf.WriteString(self.lp.SprintStyled("fg="+s, s))
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
text := strings.TrimSpace(buf.String())
|
||||
|
||||
@@ -137,10 +137,6 @@ type FileTransmissionCommand struct {
|
||||
Data []byte `json:"d,omitempty"`
|
||||
}
|
||||
|
||||
func escape_semicolons(x string) string {
|
||||
return strings.ReplaceAll(x, ";", ";;")
|
||||
}
|
||||
|
||||
var ftc_field_map = sync.OnceValue(func() map[string]reflect.StructField {
|
||||
ans := make(map[string]reflect.StructField)
|
||||
self := FileTransmissionCommand{}
|
||||
@@ -239,10 +235,10 @@ func NewFileTransmissionCommand(serialized string) (ans *FileTransmissionCommand
|
||||
return
|
||||
}
|
||||
field_map := ftc_field_map()
|
||||
key_length, key_start, val_start, val_length := 0, 0, 0, 0
|
||||
key_length, key_start, val_start := 0, 0, 0
|
||||
|
||||
handle_value := func(key, serialized_val string) error {
|
||||
key = strings.TrimLeft(key, `;;`)
|
||||
key = strings.TrimLeft(key, `;`)
|
||||
if field, ok := field_map[key]; ok {
|
||||
val := v.FieldByIndex(field.Index)
|
||||
switch val.Kind() {
|
||||
@@ -305,7 +301,7 @@ func NewFileTransmissionCommand(serialized string) (ans *FileTransmissionCommand
|
||||
}
|
||||
} else {
|
||||
if ch == ';' {
|
||||
val_length = i - val_start
|
||||
val_length := i - val_start
|
||||
if key_length > 0 && val_start > 0 {
|
||||
err = handle_value(serialized[key_start:key_start+key_length], serialized[val_start:val_start+val_length])
|
||||
if err != nil {
|
||||
@@ -315,7 +311,6 @@ func NewFileTransmissionCommand(serialized string) (ans *FileTransmissionCommand
|
||||
key_length = 0
|
||||
key_start = i + 1
|
||||
val_start = 0
|
||||
val_length = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"kitty/kittens/unicode_input"
|
||||
"kitty/tools/cli/markup"
|
||||
"kitty/tools/rsync"
|
||||
"kitty/tools/tty"
|
||||
"kitty/tools/tui"
|
||||
"kitty/tools/tui/loop"
|
||||
"kitty/tools/utils"
|
||||
@@ -145,7 +144,6 @@ type remote_file struct {
|
||||
compression_type Compression
|
||||
remote_symlink_value string
|
||||
actual_file output_file
|
||||
patch_file patch_file
|
||||
}
|
||||
|
||||
func (self *remote_file) close() (err error) {
|
||||
@@ -176,7 +174,9 @@ func (self *remote_file) Write(data []byte) (n int, err error) {
|
||||
if self.actual_file == nil {
|
||||
parent := filepath.Dir(self.expanded_local_path)
|
||||
if parent != "" {
|
||||
os.MkdirAll(parent, 0o755)
|
||||
if err = os.MkdirAll(parent, 0o755); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if self.expect_diff {
|
||||
if pf, err := new_patch_file(self.expanded_local_path, self.patcher); err != nil {
|
||||
@@ -263,7 +263,7 @@ func (self *remote_file) apply_metadata() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
os.Chmod(self.expanded_local_path, self.permissions)
|
||||
_ = os.Chmod(self.expanded_local_path, self.permissions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,8 +455,6 @@ type handler struct {
|
||||
last_data_write_id loop.IdType
|
||||
}
|
||||
|
||||
var debugprintln = tty.DebugPrintln
|
||||
|
||||
func (self *manager) send(c FileTransmissionCommand, send func(string) loop.IdType) loop.IdType {
|
||||
send(self.prefix)
|
||||
send(c.Serialize(false))
|
||||
@@ -486,7 +484,7 @@ func (self *handler) abort_with_error(err error, delay ...time.Duration) {
|
||||
self.lp.Println(`Waiting to ensure terminal cancels transfer, will quit in no more than`, d)
|
||||
self.manager.send(FileTransmissionCommand{Action: Action_cancel}, self.lp.QueueWriteString)
|
||||
self.manager.state = state_canceled
|
||||
self.lp.AddTimer(d, false, self.do_error_quit)
|
||||
_, _ = self.lp.AddTimer(d, false, self.do_error_quit)
|
||||
}
|
||||
|
||||
func (self *handler) do_error_quit(loop.IdType) error {
|
||||
@@ -713,10 +711,12 @@ func files_for_receive(opts *Options, dest string, files []*remote_file, remote_
|
||||
for spec_id, files_for_spec := range spec_map {
|
||||
spec := spec_paths[spec_id]
|
||||
tree := make_tree(files_for_spec, filepath.Dir(expand_home(spec)))
|
||||
walk_tree(tree, func(x *tree_node) error {
|
||||
if err = walk_tree(tree, func(x *tree_node) error {
|
||||
ans = append(ans, x.entry)
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
number_of_source_files := 0
|
||||
@@ -728,10 +728,12 @@ func files_for_receive(opts *Options, dest string, files []*remote_file, remote_
|
||||
if dest_is_dir {
|
||||
dest_path := filepath.Join(dest, filepath.Base(files_for_spec[0].remote_path))
|
||||
tree := make_tree(files_for_spec, filepath.Dir(expand_home(dest_path)))
|
||||
walk_tree(tree, func(x *tree_node) error {
|
||||
if err = walk_tree(tree, func(x *tree_node) error {
|
||||
ans = append(ans, x.entry)
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
f := files_for_spec[0]
|
||||
f.expanded_local_path = expand_home(dest)
|
||||
@@ -884,9 +886,13 @@ func (self *handler) on_file_transfer_response(ftc *FileTransmissionCommand) (er
|
||||
if self.manager.transfer_done {
|
||||
self.manager.send(FileTransmissionCommand{Action: Action_finish}, self.lp.QueueWriteString)
|
||||
self.quit_after_write_code = 0
|
||||
self.refresh_progress(0)
|
||||
if err = self.refresh_progress(0); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if self.transmit_started {
|
||||
self.refresh_progress(0)
|
||||
if err = self.refresh_progress(0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -984,7 +990,7 @@ func (self *handler) draw_files() {
|
||||
if p.total_transferred > 0 {
|
||||
self.render_progress(`Total`, Progress{
|
||||
spinner_char: sc, bytes_so_far: p.total_transferred, total_bytes: p.total_bytes_to_transfer,
|
||||
secs_so_far: time.Now().Sub(p.started_at).Seconds(), is_complete: is_complete,
|
||||
secs_so_far: time.Since(p.started_at).Seconds(), is_complete: is_complete,
|
||||
bytes_per_sec: safe_divide(p.transfered_stats_amt, p.transfered_stats_interval.Abs().Seconds()),
|
||||
})
|
||||
self.lp.Println()
|
||||
@@ -1049,11 +1055,15 @@ func (self *handler) on_key_event(ev *loop.KeyEvent) error {
|
||||
if self.check_paths_printed && !self.transmit_started {
|
||||
self.abort_with_error(fmt.Errorf(`Canceled by user`))
|
||||
} else {
|
||||
self.on_interrupt()
|
||||
if _, err := self.on_interrupt(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if ev.MatchesPressOrRepeat("ctrl+c") {
|
||||
ev.Handled = true
|
||||
self.on_interrupt()
|
||||
if _, err := self.on_interrupt(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1102,7 +1112,7 @@ func receive_loop(opts *Options, spec []string, dest string) (err error, rc int)
|
||||
lp.OnKeyEvent = handler.on_key_event
|
||||
lp.OnResize = func(old_sz, new_sz loop.ScreenSize) error {
|
||||
if handler.progress_drawn {
|
||||
handler.refresh_progress(0)
|
||||
return handler.refresh_progress(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -348,7 +348,7 @@ type SendManager struct {
|
||||
bypass string
|
||||
use_rsync bool
|
||||
file_progress func(*File, int)
|
||||
file_done func(*File)
|
||||
file_done func(*File) error
|
||||
fid_map map[string]*File
|
||||
all_acknowledged, all_started, has_transmitting, has_rsync bool
|
||||
active_idx int
|
||||
@@ -583,7 +583,7 @@ func (self *SendHandler) draw_progress_for_current_file(af *File, spinner_char s
|
||||
var secs_so_far time.Duration
|
||||
empty := File{}
|
||||
if af.done_at == empty.done_at {
|
||||
secs_so_far = time.Now().Sub(af.transmit_started_at)
|
||||
secs_so_far = time.Since(af.transmit_started_at)
|
||||
} else {
|
||||
secs_so_far = af.done_at.Sub(af.transmit_started_at)
|
||||
}
|
||||
@@ -611,7 +611,9 @@ func (self *SendHandler) refresh_progress(timer_id loop.IdType) (err error) {
|
||||
self.progress_update_timer = 0
|
||||
}
|
||||
if self.manager.active_file() == nil && !self.manager.all_acknowledged && self.done_file_ids.Len() != 0 && self.done_file_ids.Len() < len(self.manager.files) {
|
||||
self.transmit_next_chunk()
|
||||
if err = self.transmit_next_chunk(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
self.lp.StartAtomicUpdate()
|
||||
defer self.lp.EndAtomicUpdate()
|
||||
@@ -633,12 +635,12 @@ func (self *SendHandler) on_file_progress(f *File, change int) {
|
||||
self.schedule_progress_update(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
func (self *SendHandler) on_file_done(f *File) {
|
||||
func (self *SendHandler) on_file_done(f *File) error {
|
||||
self.done_files = append(self.done_files, f)
|
||||
if f.err_msg != "" {
|
||||
self.failed_files = append(self.failed_files, f)
|
||||
}
|
||||
self.refresh_progress(0)
|
||||
return self.refresh_progress(0)
|
||||
}
|
||||
|
||||
func (self *SendHandler) send_payload(payload string) loop.IdType {
|
||||
@@ -743,7 +745,9 @@ func (self *SendManager) on_file_status_update(ftc *FileTransmissionCommand) err
|
||||
file.err_msg = ftc.Status
|
||||
}
|
||||
self.progress_tracker.on_file_done(file)
|
||||
self.file_done(file)
|
||||
if err := self.file_done(file); err != nil {
|
||||
return err
|
||||
}
|
||||
if self.active_idx > -1 && file == self.files[self.active_idx] {
|
||||
self.active_idx = -1
|
||||
}
|
||||
@@ -857,8 +861,7 @@ func (self *SendHandler) check_for_transmit_ok() (err error) {
|
||||
return
|
||||
}
|
||||
self.transmit_ok_checked = true
|
||||
self.start_transfer()
|
||||
return
|
||||
return self.start_transfer()
|
||||
}
|
||||
|
||||
func (self *SendHandler) print_check_paths() {
|
||||
@@ -1083,7 +1086,9 @@ func (self *SendHandler) on_text(text string, from_key_event, in_bracketed_paste
|
||||
return err
|
||||
}
|
||||
if self.manager.all_acknowledged {
|
||||
self.refresh_progress(0)
|
||||
if err = self.refresh_progress(0); err != nil {
|
||||
return err
|
||||
}
|
||||
self.transfer_finished()
|
||||
}
|
||||
return nil
|
||||
@@ -1110,7 +1115,7 @@ func (self *SendHandler) abort_transfer(delay ...time.Duration) {
|
||||
}
|
||||
self.send_payload(FileTransmissionCommand{Action: Action_cancel}.Serialize())
|
||||
self.manager.state = SEND_CANCELED
|
||||
self.lp.AddTimer(d, false, func(loop.IdType) error {
|
||||
_, _ = self.lp.AddTimer(d, false, func(loop.IdType) error {
|
||||
self.lp.Quit(1)
|
||||
return nil
|
||||
})
|
||||
@@ -1118,7 +1123,7 @@ func (self *SendHandler) abort_transfer(delay ...time.Duration) {
|
||||
|
||||
func (self *SendHandler) on_resize(old_size, new_size loop.ScreenSize) error {
|
||||
if self.progress_drawn {
|
||||
self.refresh_progress(0)
|
||||
return self.refresh_progress(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1158,7 +1163,9 @@ func (self *SendHandler) on_writing_finished(msg_id loop.IdType, has_pending_wri
|
||||
} else {
|
||||
self.quit_after_write_code = 0
|
||||
}
|
||||
self.refresh_progress(0)
|
||||
if err = self.refresh_progress(0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if self.quit_after_write_code > -1 && !has_pending_writes {
|
||||
self.lp.Quit(self.quit_after_write_code)
|
||||
@@ -1168,7 +1175,9 @@ func (self *SendHandler) on_writing_finished(msg_id loop.IdType, has_pending_wri
|
||||
return self.check_for_transmit_ok()
|
||||
}
|
||||
if chunk_transmitted {
|
||||
self.refresh_progress(0)
|
||||
if err = self.refresh_progress(0); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.transmit_next_chunk()
|
||||
}
|
||||
return
|
||||
|
||||
@@ -10,7 +10,6 @@ in float bg_alpha;
|
||||
uniform sampler2DArray sprites;
|
||||
uniform float text_contrast;
|
||||
uniform float text_gamma_adjustment;
|
||||
uniform float text_fg_override_threshold;
|
||||
in float effective_text_alpha;
|
||||
in vec3 sprite_pos;
|
||||
in vec3 underline_pos;
|
||||
@@ -159,7 +158,6 @@ float adjust_alpha_for_incorrect_blending_by_compositor(float text_fg_alpha, flo
|
||||
// We apply the correction only if there was actual text at this pixel, so as to not make
|
||||
// background_opacity non-linear
|
||||
// See https://github.com/kovidgoyal/kitty/issues/6209 for discussion.
|
||||
const float threshold = 0.000001;
|
||||
// ans = text_fg_alpha * linear2srgb(final_alpha) + (1 - text_fg_alpha) * final_alpha
|
||||
return mix(final_alpha, linear2srgb(final_alpha), text_fg_alpha);
|
||||
}
|
||||
|
||||
@@ -214,6 +214,8 @@ class Child:
|
||||
self.cwd = os.path.abspath(cwd)
|
||||
self.stdin = stdin
|
||||
self.env = env or {}
|
||||
self.is_default_shell = bool(self.argv and self.argv[0] == shell_path)
|
||||
self.should_run_via_run_shell_kitten = is_macos and self.is_default_shell
|
||||
|
||||
def final_env(self) -> Dict[str, str]:
|
||||
from kitty.options.utils import DELETE_ENV_VAR
|
||||
@@ -244,7 +246,7 @@ class Child:
|
||||
if opts.forward_stdio:
|
||||
env['KITTY_STDIO_FORWARDED'] = '3'
|
||||
self.unmodified_argv = list(self.argv)
|
||||
if 'disabled' not in opts.shell_integration:
|
||||
if not self.should_run_via_run_shell_kitten and 'disabled' not in opts.shell_integration:
|
||||
from .shell_integration import modify_shell_environ
|
||||
modify_shell_environ(opts, env, self.argv)
|
||||
env = {k: v for k, v in env.items() if v is not DELETE_ENV_VAR}
|
||||
@@ -271,10 +273,10 @@ class Child:
|
||||
os.set_inheritable(stdin_read_fd, True)
|
||||
else:
|
||||
stdin_read_fd = stdin_write_fd = -1
|
||||
env = tuple(f'{k}={v}' for k, v in self.final_env().items())
|
||||
final_env = self.final_env()
|
||||
env = tuple(f'{k}={v}' for k, v in final_env.items())
|
||||
argv = list(self.argv)
|
||||
exe = argv[0]
|
||||
if is_macos and exe == shell_path:
|
||||
if self.should_run_via_run_shell_kitten:
|
||||
# bash will only source ~/.bash_profile if it detects it is a login
|
||||
# shell (see the invocation section of the bash man page), which it
|
||||
# does if argv[0] is prefixed by a hyphen see
|
||||
@@ -289,8 +291,19 @@ class Child:
|
||||
# https://github.com/kovidgoyal/kitty/issues/1870
|
||||
# xterm, urxvt, konsole and gnome-terminal do not do it in my
|
||||
# testing.
|
||||
argv[0] = (f'-{exe.split("/")[-1]}')
|
||||
self.final_exe = which(exe) or exe
|
||||
import shlex
|
||||
ksi = ' '.join(opts.shell_integration)
|
||||
if ksi == 'invalid':
|
||||
ksi = 'enabled'
|
||||
argv = [kitten_exe(), 'run-shell', '--shell', shlex.join(argv), '--shell-integration', ksi]
|
||||
if is_macos:
|
||||
# In addition for getlogin() to work we need to run the shell
|
||||
# via the /usr/bin/login wrapper, sigh.
|
||||
# https://github.com/kovidgoyal/kitty/issues/6511
|
||||
import pwd
|
||||
user = pwd.getpwuid(os.geteuid()).pw_name
|
||||
argv = ['/usr/bin/login', '-f', '-l', '-p', user] + argv
|
||||
self.final_exe = which(argv[0]) or argv[0]
|
||||
self.final_argv0 = argv[0]
|
||||
pid = fast_data_types.spawn(
|
||||
self.final_exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd,
|
||||
|
||||
@@ -22,7 +22,7 @@ class Version(NamedTuple):
|
||||
|
||||
appname: str = 'kitty'
|
||||
kitty_face = '🐱'
|
||||
version: Version = Version(0, 30, 0)
|
||||
version: Version = Version(0, 30, 1)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
|
||||
@@ -12,12 +12,11 @@ from .child import Child
|
||||
from .cli import parse_args
|
||||
from .cli_stub import LaunchCLIOptions
|
||||
from .clipboard import set_clipboard_string, set_primary_selection
|
||||
from .constants import shell_path
|
||||
from .fast_data_types import add_timer, get_boss, get_options, get_os_window_title, patch_color_profiles
|
||||
from .options.utils import env as parse_env
|
||||
from .tabs import Tab, TabManager
|
||||
from .types import OverlayType, run_once
|
||||
from .utils import cmdline_for_hold, get_editor, log_error, resolve_custom_file, which
|
||||
from .utils import cmdline_for_hold, get_editor, log_error, resolve_custom_file, resolved_shell, which
|
||||
from .window import CwdRequest, CwdRequestType, Watchers, Window
|
||||
|
||||
try:
|
||||
@@ -591,7 +590,7 @@ def _launch(
|
||||
set_primary_selection(stdin)
|
||||
else:
|
||||
if opts.hold:
|
||||
cmd = kw['cmd'] or [shell_path]
|
||||
cmd = kw['cmd'] or resolved_shell()
|
||||
if not os.path.isabs(cmd[0]):
|
||||
cmd[0] = which(cmd[0]) or cmd[0]
|
||||
kw['cmd'] = cmdline_for_hold(cmd)
|
||||
@@ -907,6 +906,12 @@ def clone_and_launch(msg: str, window: Window) -> None:
|
||||
cmdline = []
|
||||
if not cmdline:
|
||||
cmdline = list(window.child.argv)
|
||||
if cmdline and cmdline[0].startswith('-'): # on macOS, run via run-shell kitten
|
||||
if window.child.is_default_shell:
|
||||
cmdline = window.child.unmodified_argv
|
||||
else:
|
||||
cmdline[0] = cmdline[0][1:]
|
||||
cmdline[0] = which(cmdline[0]) or cmdline[0]
|
||||
if cmdline and cmdline[0] == window.child.final_argv0:
|
||||
cmdline[0] = window.child.final_exe
|
||||
if cmdline and cmdline == [window.child.final_exe] + window.child.argv[1:]:
|
||||
|
||||
@@ -267,7 +267,7 @@ action launch --type=os-window $EDITOR $FILE_PATH
|
||||
# Open image files with icat
|
||||
protocol file
|
||||
mime image/*
|
||||
action launch --type=os-window kitty +kitten icat --hold $FILE_PATH
|
||||
action launch --type=os-window kitten icat --hold $FILE_PATH
|
||||
|
||||
# Open ssh URLs with ssh command
|
||||
protocol ssh
|
||||
|
||||
@@ -2782,7 +2782,7 @@ The shell program to execute. The default value of :code:`.` means to use
|
||||
whatever shell is set as the default shell for the current user. Note that on
|
||||
macOS if you change this, you might need to add :code:`--login` and
|
||||
:code:`--interactive` to ensure that the shell starts in interactive mode and
|
||||
reads its startup rc files.
|
||||
reads its startup rc files. Environment variables are expanded in this setting.
|
||||
'''
|
||||
)
|
||||
|
||||
@@ -2832,7 +2832,7 @@ Glob patterns can be used too, for example::
|
||||
|
||||
To get a list of available actions, run::
|
||||
|
||||
kitty @ --help
|
||||
kitten @ --help
|
||||
|
||||
A set of actions to be allowed when no password is sent can be specified by
|
||||
using an empty password. For example::
|
||||
@@ -3028,7 +3028,7 @@ jumping to previous prompts, browsing the output of the previous command in a
|
||||
pager, etc. on supported shells. Set to :code:`disabled` to turn off shell
|
||||
integration, completely. It is also possible to disable individual features, set
|
||||
to a space separated list of these values: :code:`no-rc`, :code:`no-cursor`,
|
||||
:code:`no-title`, :code:`no-cwd`, :code:`no-prompt-mark`, :code:`no-complete`.
|
||||
:code:`no-title`, :code:`no-cwd`, :code:`no-prompt-mark`, :code:`no-complete`, :code:`no-sudo`.
|
||||
See :ref:`Shell integration <shell_integration>` for details.
|
||||
'''
|
||||
)
|
||||
@@ -4111,7 +4111,7 @@ This will send "Special text" when you press the :kbd:`Ctrl+Alt+A` key
|
||||
combination. The text to be sent decodes :link:`ANSI C escapes <https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html>`
|
||||
so you can use escapes like :code:`\\\\e` to send control codes or :code:`\\\\u21fb` to send
|
||||
Unicode characters (or you can just input the Unicode characters directly as
|
||||
UTF-8 text). You can use ``kitty +kitten show_key`` to get the key escape
|
||||
UTF-8 text). You can use ``kitten show_key`` to get the key escape
|
||||
codes you want to emulate.
|
||||
|
||||
The first argument to :code:`send_text` is the keyboard modes in which to
|
||||
|
||||
@@ -863,7 +863,7 @@ def store_multiple(val: str, current_val: Container[str]) -> Iterable[Tuple[str,
|
||||
yield val, val
|
||||
|
||||
|
||||
allowed_shell_integration_values = frozenset({'enabled', 'disabled', 'no-rc', 'no-cursor', 'no-title', 'no-prompt-mark', 'no-complete', 'no-cwd'})
|
||||
allowed_shell_integration_values = frozenset({'enabled', 'disabled', 'no-rc', 'no-cursor', 'no-title', 'no-prompt-mark', 'no-complete', 'no-cwd', 'no-sudo'})
|
||||
|
||||
|
||||
def shell_integration(x: str) -> FrozenSet[str]:
|
||||
|
||||
6
kitty/parse-graphics-command.h
generated
6
kitty/parse-graphics-command.h
generated
@@ -302,9 +302,9 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
g.payload_sz = sizeof(payload);
|
||||
if (!base64_decode32(screen->parser_buf + pos, sz, payload,
|
||||
&g.payload_sz)) {
|
||||
REPORT_ERROR(
|
||||
"Failed to parse GraphicsCommand command payload with error: %s",
|
||||
"output buffer for base64_decode too small");
|
||||
REPORT_ERROR("Failed to parse GraphicsCommand command payload with "
|
||||
"error: payload size (%zu) too large",
|
||||
sz);
|
||||
return;
|
||||
}
|
||||
pos = screen->parser_buf_pos;
|
||||
|
||||
@@ -82,7 +82,10 @@ class SharedMemory:
|
||||
self._name = name
|
||||
try:
|
||||
if flags & os.O_CREAT and size:
|
||||
os.ftruncate(self._fd, size)
|
||||
if hasattr(os, 'posix_fallocate'):
|
||||
os.posix_fallocate(self._fd, 0, size)
|
||||
else:
|
||||
os.ftruncate(self._fd, size)
|
||||
self.stats = os.fstat(self._fd)
|
||||
size = self.stats.st_size
|
||||
self._mmap = mmap.mmap(self._fd, size, access=mmap.ACCESS_READ if readonly else mmap.ACCESS_WRITE)
|
||||
|
||||
@@ -81,6 +81,7 @@ class TabDict(TypedDict):
|
||||
layout_opts: Dict[str, Any]
|
||||
enabled_layouts: List[str]
|
||||
windows: List[WindowDict]
|
||||
groups: List[Dict[str, Any]]
|
||||
active_window_history: List[int]
|
||||
|
||||
|
||||
@@ -781,6 +782,9 @@ class Tab: # {{{
|
||||
is_focused=w.os_window_id == current_focused_os_window_id() and w is active_window,
|
||||
is_self=w is self_window)
|
||||
|
||||
def list_groups(self) -> List[Dict[str, Any]]:
|
||||
return [g.as_simple_dict() for g in self.windows.groups]
|
||||
|
||||
def matches_query(self, field: str, query: str, active_tab_manager: Optional['TabManager'] = None) -> bool:
|
||||
if field == 'title':
|
||||
return re.search(query, self.effective_title) is not None
|
||||
@@ -1036,6 +1040,7 @@ class TabManager: # {{{
|
||||
'layout_opts': tab.current_layout.layout_opts.serialized(),
|
||||
'enabled_layouts': tab.enabled_layouts,
|
||||
'windows': windows,
|
||||
'groups': tab.list_groups(),
|
||||
'active_window_history': list(tab.windows.active_window_history),
|
||||
}
|
||||
|
||||
|
||||
@@ -771,7 +771,19 @@ def resolved_shell(opts: Optional[Options] = None) -> List[str]:
|
||||
ans = [shell_path]
|
||||
else:
|
||||
import shlex
|
||||
ans = shlex.split(q)
|
||||
env = {}
|
||||
if opts is not None:
|
||||
env['TERM'] = opts.term
|
||||
if 'SHELL' not in os.environ:
|
||||
env['SHELL'] = shell_path
|
||||
if 'HOME' not in os.environ:
|
||||
env['HOME'] = os.path.expanduser('~')
|
||||
if 'USER' not in os.environ:
|
||||
import pwd
|
||||
env['USER'] = pwd.getpwuid(os.geteuid()).pw_name
|
||||
def expand(x: str) -> str:
|
||||
return expandvars(x, env)
|
||||
ans = list(map(expand, shlex.split(q)))
|
||||
return ans
|
||||
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@ class WindowDict(TypedDict):
|
||||
lines: int
|
||||
columns: int
|
||||
user_vars: Dict[str, str]
|
||||
at_prompt: bool
|
||||
|
||||
|
||||
class PipeData(TypedDict):
|
||||
@@ -648,6 +649,7 @@ class Window:
|
||||
'env': self.child.environ,
|
||||
'foreground_processes': self.child.foreground_processes,
|
||||
'is_self': is_self,
|
||||
'at_prompt': self.at_prompt,
|
||||
'lines': self.screen.lines,
|
||||
'columns': self.screen.columns,
|
||||
'user_vars': self.user_vars,
|
||||
|
||||
@@ -91,6 +91,12 @@ class WindowGroup:
|
||||
'windows': [w.serialize_state() for w in self.windows]
|
||||
}
|
||||
|
||||
def as_simple_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'id': self.id,
|
||||
'windows': [w.id for w in self.windows],
|
||||
}
|
||||
|
||||
def decoration(self, which: EdgeLiteral, border_mult: int = 1, is_single_window: bool = False) -> int:
|
||||
if not self.windows:
|
||||
return 0
|
||||
|
||||
1
setup.py
1
setup.py
@@ -1383,6 +1383,7 @@ def macos_info_plist() -> bytes:
|
||||
# Security
|
||||
NSAppleEventsUsageDescription=access('AppleScript.'),
|
||||
NSSystemAdministrationUsageDescription=access('elevated privileges.', 'requires'),
|
||||
NSBluetoothAlwaysUsageDescription=access('Bluetooth.'),
|
||||
# Speech
|
||||
NSSpeechRecognitionUsageDescription=access('speech recognition.'),
|
||||
)
|
||||
|
||||
@@ -75,7 +75,7 @@ fi
|
||||
# which is not available on older bash
|
||||
builtin declare -A _ksi_prompt
|
||||
_ksi_prompt=(
|
||||
[cursor]='y' [title]='y' [mark]='y' [complete]='y' [cwd]='y' [ps0]='' [ps0_suffix]='' [ps1]='' [ps1_suffix]='' [ps2]=''
|
||||
[cursor]='y' [title]='y' [mark]='y' [complete]='y' [cwd]='y' [sudo]='y' [ps0]='' [ps0_suffix]='' [ps1]='' [ps1_suffix]='' [ps2]=''
|
||||
[hostname_prefix]='' [sourced]='y' [last_reported_cwd]=''
|
||||
)
|
||||
|
||||
@@ -89,6 +89,7 @@ _ksi_main() {
|
||||
"no-prompt-mark") _ksi_prompt[mark]='n';;
|
||||
"no-complete") _ksi_prompt[complete]='n';;
|
||||
"no-cwd") _ksi_prompt[cwd]='n';;
|
||||
"no-sudo") _ksi_prompt[sudo]='n';;
|
||||
esac
|
||||
done
|
||||
IFS="$ifs"
|
||||
@@ -211,6 +212,11 @@ _ksi_main() {
|
||||
|
||||
builtin alias edit-in-kitty="kitten edit-in-kitty"
|
||||
|
||||
if [[ "${_ksi_prompt[sudo]}" == "y" ]]; then
|
||||
# Ensure terminfo is available in sudo
|
||||
[[ -n "$TERMINFO" ]] && builtin alias sudo="sudo TERMINFO=\"$TERMINFO\""
|
||||
fi
|
||||
|
||||
if [[ "${_ksi_prompt[complete]}" == "y" ]]; then
|
||||
_ksi_completions() {
|
||||
builtin local src
|
||||
|
||||
@@ -108,6 +108,13 @@ function __ksi_schedule --on-event fish_prompt -d "Setup kitty integration after
|
||||
__update_cwd_osc
|
||||
end
|
||||
|
||||
if not contains "no-sudo" $_ksi
|
||||
# Ensure terminfo is available in sudo
|
||||
if test -n "$TERMINFO"
|
||||
alias sudo="sudo TERMINFO=\"$TERMINFO\""
|
||||
end
|
||||
end
|
||||
|
||||
# Handle clone launches
|
||||
if test -n "$KITTY_IS_CLONE_LAUNCH"
|
||||
set --local orig_conda_env "$CONDA_DEFAULT_ENV"
|
||||
|
||||
@@ -240,6 +240,12 @@ exec_login_shell() {
|
||||
execute_with_perl
|
||||
execute_sh_with_posix_env
|
||||
exec "$login_shell" "-l"
|
||||
printf "%s\n" "Could not execute the shell $login_shell as a login shell" > /dev/stderr
|
||||
if [ -e /dev/stderr ]; then
|
||||
printf "%s\n" "Could not execute the shell $login_shell as a login shell" > /dev/stderr
|
||||
elif [ -e /dev/fd/2 ]; then
|
||||
printf "%s\n" "Could not execute the shell $login_shell as a login shell" > /dev/fd/2
|
||||
else
|
||||
printf "%s\n" "Could not execute the shell $login_shell as a login shell"
|
||||
fi
|
||||
exec "$login_shell"
|
||||
}
|
||||
|
||||
@@ -162,9 +162,10 @@ def compile_terminfo(base):
|
||||
[tic, '-x', '-o', os.path.join(base, tname), os.path.join(base, '.terminfo', 'kitty.terminfo')],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
||||
)
|
||||
output = p.stdout.read()
|
||||
rc = p.wait()
|
||||
if rc != 0:
|
||||
getattr(sys.stderr, 'buffer', sys.stderr).write(p.stdout)
|
||||
getattr(sys.stderr, 'buffer', sys.stderr).write(output)
|
||||
raise SystemExit('Failed to compile the terminfo database')
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,17 @@ cleanup_on_bootstrap_exit() {
|
||||
tdir=""
|
||||
}
|
||||
|
||||
die() { printf "\033[31m%s\033[m\n\r" "$*" > /dev/stderr; cleanup_on_bootstrap_exit; exit 1; }
|
||||
die() {
|
||||
if [ -e /dev/stderr ]; then
|
||||
printf "\033[31m%s\033[m\n\r" "$*" > /dev/stderr;
|
||||
elif [ -e /dev/fd/2 ]; then
|
||||
printf "\033[31m%s\033[m\n\r" "$*" > /dev/fd/2;
|
||||
else
|
||||
printf "\033[31m%s\033[m\n\r" "$*";
|
||||
fi
|
||||
cleanup_on_bootstrap_exit;
|
||||
exit 1;
|
||||
}
|
||||
|
||||
python_detected="0"
|
||||
detect_python() {
|
||||
|
||||
@@ -6,7 +6,16 @@
|
||||
{ \unalias command; \unset -f command; } >/dev/null 2>&1
|
||||
|
||||
|
||||
die() { printf "\033[31m%s\033[m\n\r" "$*" > /dev/stderr; exit 1; }
|
||||
die() {
|
||||
if [ -e /dev/stderr ]; then
|
||||
printf "\033[31m%s\033[m\n\r" "$*" > /dev/stderr;
|
||||
elif [ -e /dev/fd/2 ]; then
|
||||
printf "\033[31m%s\033[m\n\r" "$*" > /dev/fd/2;
|
||||
else
|
||||
printf "\033[31m%s\033[m\n\r" "$*";
|
||||
fi
|
||||
exit 1;
|
||||
}
|
||||
|
||||
exec_kitty() {
|
||||
[ -n "$kitty_exe" ] && exec "$kitty_exe" "$@"
|
||||
@@ -63,7 +72,7 @@ case "$(command uname -m)" in
|
||||
amd64|x86_64) arch="amd64";;
|
||||
aarch64*) arch="arm64";;
|
||||
armv8*) arch="arm64";;
|
||||
arm) arch="arm";;
|
||||
arm|armv7l) arch="arm";;
|
||||
i386) arch="386";;
|
||||
i686) arch="386";;
|
||||
*) die "Unknown CPU architecture $(command uname -m)";;
|
||||
|
||||
@@ -84,7 +84,7 @@ precmd_functions+=(_ksi_deferred_init)
|
||||
_ksi_deferred_init() {
|
||||
builtin emulate -L zsh -o no_warn_create_global -o no_aliases
|
||||
|
||||
# Recognized options: no-cursor, no-title, no-prompt-mark, no-complete, no-cwd.
|
||||
# Recognized options: no-cursor, no-title, no-prompt-mark, no-complete, no-cwd, no-sudo.
|
||||
builtin local -a opt
|
||||
opt=(${(s: :)KITTY_SHELL_INTEGRATION})
|
||||
builtin unset KITTY_SHELL_INTEGRATION
|
||||
@@ -388,6 +388,11 @@ _ksi_deferred_init() {
|
||||
|
||||
builtin alias edit-in-kitty="kitten edit-in-kitty"
|
||||
|
||||
if (( ! opt[(Ie)no-sudo] )); then
|
||||
# Ensure terminfo is available in sudo
|
||||
[[ -n "$TERMINFO" ]] && builtin alias sudo="sudo TERMINFO=\"$TERMINFO\""
|
||||
fi
|
||||
|
||||
# Map alt+left/right to move by word if not already mapped. This is expected behavior on macOS and I am tired
|
||||
# of answering questions about it.
|
||||
[[ $(builtin bindkey "^[[1;3C") == *" undefined-key" ]] && builtin bindkey "^[[1;3C" "forward-word"
|
||||
|
||||
@@ -19,6 +19,7 @@ type Options struct {
|
||||
Shell string
|
||||
ShellIntegration string
|
||||
Env []string
|
||||
Cwd string
|
||||
}
|
||||
|
||||
func main(args []string, opts *Options) (rc int, err error) {
|
||||
@@ -53,7 +54,7 @@ func main(args []string, opts *Options) (rc int, err error) {
|
||||
os.Setenv("TERMINFO_DIRS", terminfo_dir+existing)
|
||||
}
|
||||
}
|
||||
err = tui.RunShell(tui.ResolveShell(opts.Shell), tui.ResolveShellIntegration(opts.ShellIntegration))
|
||||
err = tui.RunShell(tui.ResolveShell(opts.Shell), tui.ResolveShellIntegration(opts.ShellIntegration), opts.Cwd)
|
||||
if changed {
|
||||
os.Clearenv()
|
||||
for _, entry := range env_before {
|
||||
@@ -96,5 +97,10 @@ func EntryPoint(root *cli.Command) *cli.Command {
|
||||
Help: "Specify an env var to set before running the shell. Of the form KEY=VAL. Can be specified multiple times. If no = is present KEY is unset.",
|
||||
Type: "list",
|
||||
})
|
||||
sc.Add(cli.OptionSpec{
|
||||
Name: "--cwd",
|
||||
Help: "The working directory to use when executing the shell.",
|
||||
})
|
||||
|
||||
return sc
|
||||
}
|
||||
|
||||
@@ -51,7 +51,9 @@ globin
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = p.ParseOverrides("over one", "over two")
|
||||
if err = p.ParseOverrides("over one", "over two"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
diff := cmp.Diff([]string{"a one", "incb cool", "b ", "inc1 cool", "inc2 cool", "env cool", "inc notcool", "over one", "over two"}, parsed_lines)
|
||||
if diff != "" {
|
||||
t.Fatalf("Unexpected parsed config values:\n%s", diff)
|
||||
|
||||
@@ -293,12 +293,8 @@ func (r *rsync) ApplyDelta(output io.Writer, target io.ReadSeeker, op Operation)
|
||||
if _, err = target.Seek(int64(r.BlockSize*int(op.BlockIndex)), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
n, err = io.ReadAtLeast(target, buffer, r.BlockSize)
|
||||
if err != nil {
|
||||
if err != io.ErrUnexpectedEOF {
|
||||
return err
|
||||
}
|
||||
err = nil
|
||||
if n, err = io.ReadAtLeast(target, buffer, r.BlockSize); err != nil && err != io.ErrUnexpectedEOF {
|
||||
return err
|
||||
}
|
||||
block = buffer[:n]
|
||||
return write(block)
|
||||
|
||||
@@ -336,7 +336,9 @@ func set_comment_in_zip_file(path string, comment string) error {
|
||||
defer src.Close()
|
||||
buf := bytes.Buffer{}
|
||||
dest := zip.NewWriter(&buf)
|
||||
dest.SetComment(comment)
|
||||
if err = dest.SetComment(comment); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sf := range src.File {
|
||||
err = dest.Copy(sf)
|
||||
if err != nil {
|
||||
@@ -344,8 +346,7 @@ func set_comment_in_zip_file(path string, comment string) error {
|
||||
}
|
||||
}
|
||||
dest.Close()
|
||||
utils.AtomicUpdateFile(path, buf.Bytes(), 0o644)
|
||||
return nil
|
||||
return utils.AtomicUpdateFile(path, buf.Bytes(), 0o644)
|
||||
}
|
||||
|
||||
func fetch_cached(name, url, cache_path string, max_cache_age time.Duration) (string, error) {
|
||||
@@ -358,15 +359,16 @@ func fetch_cached(name, url, cache_path string, max_cache_age time.Duration) (st
|
||||
var jm JSONMetadata
|
||||
if err == nil {
|
||||
defer zf.Close()
|
||||
err = json.Unmarshal(utils.UnsafeStringToBytes(zf.Comment), &jm)
|
||||
if max_cache_age < 0 {
|
||||
return cache_path, nil
|
||||
}
|
||||
cache_age, err := utils.ISO8601Parse(jm.Timestamp)
|
||||
if err == nil {
|
||||
if time.Now().Before(cache_age.Add(max_cache_age)) {
|
||||
if err = json.Unmarshal(utils.UnsafeStringToBytes(zf.Comment), &jm); err == nil {
|
||||
if max_cache_age < 0 {
|
||||
return cache_path, nil
|
||||
}
|
||||
cache_age, err := utils.ISO8601Parse(jm.Timestamp)
|
||||
if err == nil {
|
||||
if time.Now().Before(cache_age.Add(max_cache_age)) {
|
||||
return cache_path, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if max_cache_age < 0 {
|
||||
@@ -429,7 +431,9 @@ func fetch_cached(name, url, cache_path string, max_cache_age time.Duration) (st
|
||||
jm.Etag = resp.Header.Get("ETag")
|
||||
jm.Timestamp = utils.ISO8601Format(time.Now())
|
||||
comment, _ := json.Marshal(jm)
|
||||
w.SetComment(utils.UnsafeBytesToString(comment))
|
||||
if err = w.SetComment(utils.UnsafeBytesToString(comment)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, file := range r.File {
|
||||
err = w.Copy(file)
|
||||
if err != nil {
|
||||
@@ -466,8 +470,9 @@ type ThemeMetadata struct {
|
||||
|
||||
func ParseThemeMetadata(path string) (*ThemeMetadata, map[string]string, error) {
|
||||
var in_metadata, in_blurb, finished_metadata bool
|
||||
ans := ThemeMetadata{}
|
||||
ans := ThemeMetadata{Is_dark: true} // the default background in kitty is dark
|
||||
settings := map[string]string{}
|
||||
|
||||
read_is_dark := func(key, val string) (err error) {
|
||||
settings[key] = val
|
||||
if key == "background" {
|
||||
@@ -475,7 +480,7 @@ func ParseThemeMetadata(path string) (*ThemeMetadata, map[string]string, error)
|
||||
if val != "" {
|
||||
bg, err := style.ParseColor(val)
|
||||
if err == nil {
|
||||
ans.Is_dark = utils.Max(bg.Red, bg.Green, bg.Green) < 115
|
||||
ans.Is_dark = utils.Max(bg.Red, bg.Green, bg.Blue) < 115
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -620,7 +625,7 @@ type ReloadDestination string
|
||||
|
||||
const (
|
||||
RELOAD_IN_PARENT ReloadDestination = "parent"
|
||||
RELOAD_IN_ALL = "all"
|
||||
RELOAD_IN_ALL ReloadDestination = "all"
|
||||
)
|
||||
|
||||
func reload_config(reload_in ReloadDestination) bool {
|
||||
@@ -637,7 +642,7 @@ func reload_config(reload_in ReloadDestination) bool {
|
||||
if all, err := process.Processes(); err == nil {
|
||||
for _, p := range all {
|
||||
if c, err := p.CmdlineSlice(); err == nil && is_kitty_gui_cmdline(c...) {
|
||||
p.SendSignal(unix.SIGUSR1)
|
||||
_ = p.SendSignal(unix.SIGUSR1)
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -656,7 +661,7 @@ func (self *Theme) SaveInDir(dirpath string) (err error) {
|
||||
}
|
||||
|
||||
func (self *Theme) SaveInConf(config_dir, reload_in, config_file_name string) (err error) {
|
||||
os.MkdirAll(config_dir, 0o755)
|
||||
_ = os.MkdirAll(config_dir, 0o755)
|
||||
path := filepath.Join(config_dir, `current-theme.conf`)
|
||||
code, err := self.Code()
|
||||
if err != nil {
|
||||
@@ -666,7 +671,10 @@ func (self *Theme) SaveInConf(config_dir, reload_in, config_file_name string) (e
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
confpath := filepath.Join(config_dir, config_file_name)
|
||||
confpath := config_file_name
|
||||
if !filepath.IsAbs(config_file_name) {
|
||||
confpath = filepath.Join(config_dir, config_file_name)
|
||||
}
|
||||
if q, err := filepath.EvalSymlinks(confpath); err == nil {
|
||||
confpath = q
|
||||
}
|
||||
@@ -676,7 +684,7 @@ func (self *Theme) SaveInConf(config_dir, reload_in, config_file_name string) (e
|
||||
}
|
||||
nraw := patch_conf(utils.UnsafeBytesToString(raw), self.metadata.Name)
|
||||
if len(raw) > 0 {
|
||||
os.WriteFile(confpath+".bak", raw, 0o600)
|
||||
_ = os.WriteFile(confpath+".bak", raw, 0o600)
|
||||
}
|
||||
err = utils.AtomicUpdateFile(confpath, utils.UnsafeStringToBytes(nraw), 0o600)
|
||||
if err != nil {
|
||||
|
||||
@@ -35,7 +35,9 @@ func TestThemeCollections(t *testing.T) {
|
||||
tdir := t.TempDir()
|
||||
|
||||
pt := func(expected ThemeMetadata, lines ...string) {
|
||||
os.WriteFile(filepath.Join(tdir, "temp.conf"), []byte(strings.Join(lines, "\n")), 0o600)
|
||||
if err := os.WriteFile(filepath.Join(tdir, "temp.conf"), []byte(strings.Join(lines, "\n")), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual, _, err := ParseThemeMetadata(filepath.Join(tdir, "temp.conf"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -46,14 +48,16 @@ func TestThemeCollections(t *testing.T) {
|
||||
}
|
||||
pt(ThemeMetadata{Name: "XYZ", Blurb: "a b", Author: "A", Is_dark: true, Num_settings: 2},
|
||||
"# some crap", " ", "## ", "## author: A", "## name: XYZ", "## blurb: a", "## b", "", "color red", "background black", "include inc.conf")
|
||||
os.WriteFile(filepath.Join(tdir, "inc.conf"), []byte("background white"), 0o600)
|
||||
if err := os.WriteFile(filepath.Join(tdir, "inc.conf"), []byte("background white"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pt(ThemeMetadata{Name: "XYZ", Blurb: "a b", Author: "A", Num_settings: 2},
|
||||
"# some crap", " ", "## ", "## author: A", "## name: XYZ", "## blurb: a", "## b", "", "color red", "background black", "include inc.conf")
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
zw := zip.NewWriter(&buf)
|
||||
fw, _ := zw.Create("x/themes.json")
|
||||
fw.Write([]byte(`[
|
||||
if _, err := fw.Write([]byte(`[
|
||||
{
|
||||
"author": "X Y",
|
||||
"blurb": "A dark color scheme for the kitty terminal.",
|
||||
@@ -67,11 +71,17 @@ func TestThemeCollections(t *testing.T) {
|
||||
{
|
||||
"name": "Empty", "file": "empty.conf"
|
||||
}
|
||||
]`))
|
||||
]`)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fw, _ = zw.Create("x/empty.conf")
|
||||
fw.Write([]byte("empty"))
|
||||
if _, err := fw.Write([]byte("empty")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fw, _ = zw.Create("x/themes/Alabaster_Dark.conf")
|
||||
fw.Write([]byte("alabaster"))
|
||||
if _, err := fw.Write([]byte("alabaster")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
zw.Close()
|
||||
|
||||
received_etag := ""
|
||||
@@ -91,8 +101,7 @@ func TestThemeCollections(t *testing.T) {
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
_, err := fetch_cached("test", ts.URL, tdir, 0)
|
||||
if err != nil {
|
||||
if _, err := fetch_cached("test", ts.URL, tdir, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, err := zip.OpenReader(filepath.Join(tdir, "test.zip"))
|
||||
|
||||
@@ -179,6 +179,9 @@ func (self *Term) set_termios_attrs(when uintptr, modify func(*unix.Termios)) (e
|
||||
}
|
||||
|
||||
func (self *Term) ApplyOperations(when uintptr, operations ...TermiosOperation) (err error) {
|
||||
if len(operations) == 0 {
|
||||
return
|
||||
}
|
||||
return self.set_termios_attrs(when, func(t *unix.Termios) {
|
||||
for _, op := range operations {
|
||||
op(t)
|
||||
@@ -214,7 +217,7 @@ func (self *Term) Restore() error {
|
||||
}
|
||||
|
||||
func (self *Term) RestoreAndClose() error {
|
||||
self.Restore()
|
||||
_ = self.Restore()
|
||||
return self.Close()
|
||||
}
|
||||
|
||||
@@ -240,7 +243,9 @@ func (self *Term) SuspendAndRun(callback func() error) error {
|
||||
return err
|
||||
}
|
||||
err = callback()
|
||||
resume()
|
||||
if rerr := resume(); rerr != nil {
|
||||
err = rerr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -323,21 +328,25 @@ func (self *Term) DebugPrintln(a ...any) {
|
||||
chunk := msg[i:end]
|
||||
encoded = encoded[:cap(encoded)]
|
||||
base64.StdEncoding.Encode(encoded, chunk)
|
||||
self.WriteString("\x1bP@kitty-print|")
|
||||
self.Write(encoded)
|
||||
self.WriteString("\x1b\\")
|
||||
_, _ = self.WriteString("\x1bP@kitty-print|")
|
||||
_, _ = self.Write(encoded)
|
||||
_, _ = self.WriteString("\x1b\\")
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Term) GetSize() (*unix.Winsize, error) {
|
||||
func GetSize(fd int) (*unix.Winsize, error) {
|
||||
for {
|
||||
sz, err := unix.IoctlGetWinsize(self.Fd(), unix.TIOCGWINSZ)
|
||||
sz, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||
if err != unix.EINTR {
|
||||
return sz, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Term) GetSize() (*unix.Winsize, error) {
|
||||
return GetSize(self.Fd())
|
||||
}
|
||||
|
||||
// go doesn't have a wrapper for ctermid()
|
||||
func Ctermid() string { return "/dev/tty" }
|
||||
|
||||
@@ -363,25 +372,3 @@ func DebugPrintln(a ...any) {
|
||||
term.DebugPrintln(a...)
|
||||
}
|
||||
}
|
||||
|
||||
func DrainControllingTTY(wait_for time.Duration) {
|
||||
tty, err := OpenControllingTerm(SetRaw, SetNoEcho)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sel := utils.CreateSelect(1)
|
||||
sel.RegisterRead(tty.Fd())
|
||||
for {
|
||||
n, err := sel.Wait(wait_for)
|
||||
if err == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n > 0 && sel.IsReadyToRead(tty.Fd()) {
|
||||
tty.Read(make([]byte, 256))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +183,9 @@ func DownloadFileWithProgress(destpath, url string, kill_if_signaled bool) (err
|
||||
return lp.OnWakeup()
|
||||
}
|
||||
|
||||
lp.AddTimer(rd.spinner.interval, true, on_timer_tick)
|
||||
if _, err = lp.AddTimer(rd.spinner.interval, true, on_timer_tick); err != nil {
|
||||
return
|
||||
}
|
||||
err = lp.Run()
|
||||
dl_data.mutex.Lock()
|
||||
if dl_data.temp_file_path != "" && !dl_data.download_finished {
|
||||
|
||||
@@ -39,7 +39,7 @@ func (self *temp_resource) remove() {
|
||||
self.path = ""
|
||||
}
|
||||
if self.mmap != nil {
|
||||
self.mmap.Unlink()
|
||||
_ = self.mmap.Unlink()
|
||||
self.mmap = nil
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,7 @@ func (self *ImageCollection) ResizeForPageSize(width, height int) {
|
||||
func (self *ImageCollection) DeleteAllVisiblePlacements(lp *loop.Loop) {
|
||||
g := self.new_graphics_command()
|
||||
g.SetAction(GRT_action_delete).SetDelete(GRT_delete_visible)
|
||||
g.WriteWithPayloadToLoop(lp, nil)
|
||||
_ = g.WriteWithPayloadToLoop(lp, nil)
|
||||
}
|
||||
|
||||
func (self *ImageCollection) PlaceImageSubRect(lp *loop.Loop, key string, page_size Size, left, top, width, height int) {
|
||||
@@ -179,7 +179,7 @@ func (self *ImageCollection) PlaceImageSubRect(lp *loop.Loop, key string, page_s
|
||||
gc := self.new_graphics_command()
|
||||
gc.SetAction(GRT_action_display).SetLeftEdge(uint64(left)).SetTopEdge(uint64(top)).SetWidth(uint64(width)).SetHeight(uint64(height))
|
||||
gc.SetImageId(r.image_id).SetPlacementId(1).SetCursorMovement(GRT_cursor_static)
|
||||
gc.WriteWithPayloadToLoop(lp, nil)
|
||||
_ = gc.WriteWithPayloadToLoop(lp, nil)
|
||||
}
|
||||
|
||||
func (self *ImageCollection) Initialize(lp *loop.Loop) {
|
||||
@@ -193,15 +193,16 @@ func (self *ImageCollection) Initialize(lp *loop.Loop) {
|
||||
g1 := self.new_graphics_command()
|
||||
g1.SetTransmission(t).SetAction(GRT_action_query).SetImageId(self.image_id_counter).SetDataWidth(1).SetDataHeight(1).SetFormat(
|
||||
GRT_format_rgb).SetDataSize(uint64(len(payload)))
|
||||
g1.WriteWithPayloadToLoop(lp, utils.UnsafeStringToBytes(payload))
|
||||
_ = g1.WriteWithPayloadToLoop(lp, utils.UnsafeStringToBytes(payload))
|
||||
return self.image_id_counter
|
||||
}
|
||||
tf, err := images.CreateTempInRAM()
|
||||
if err == nil {
|
||||
tf.Write([]byte{1, 2, 3})
|
||||
if _, err = tf.Write([]byte{1, 2, 3}); err == nil {
|
||||
self.detection_file_id = g(GRT_transmission_tempfile, tf.Name())
|
||||
self.temp_file_map[self.detection_file_id] = &temp_resource{path: tf.Name()}
|
||||
}
|
||||
tf.Close()
|
||||
self.detection_file_id = g(GRT_transmission_tempfile, tf.Name())
|
||||
self.temp_file_map[self.detection_file_id] = &temp_resource{path: tf.Name()}
|
||||
}
|
||||
sf, err := shm.CreateTemp("icat-", 3)
|
||||
if err == nil {
|
||||
@@ -222,7 +223,7 @@ func (self *ImageCollection) Finalize(lp *loop.Loop) {
|
||||
if r.image_id > 0 {
|
||||
g := self.new_graphics_command()
|
||||
g.SetAction(GRT_action_delete).SetDelete(GRT_free_by_id).SetImageId(r.image_id)
|
||||
g.WriteWithPayloadToLoop(lp, nil)
|
||||
_ = g.WriteWithPayloadToLoop(lp, nil)
|
||||
}
|
||||
}
|
||||
img.renderings = nil
|
||||
@@ -333,7 +334,7 @@ func transmit_by_escape_code(lp *loop.Loop, image_id uint32, temp_file_map map[u
|
||||
atomic := lp.IsAtomicUpdateActive()
|
||||
lp.EndAtomicUpdate()
|
||||
gc.SetTransmission(GRT_transmission_direct)
|
||||
gc.WriteWithPayloadToLoop(lp, frame.Data())
|
||||
_ = gc.WriteWithPayloadToLoop(lp, frame.Data())
|
||||
if atomic {
|
||||
lp.StartAtomicUpdate()
|
||||
}
|
||||
@@ -348,7 +349,7 @@ func transmit_by_shm(lp *loop.Loop, image_id uint32, temp_file_map map[uint32]*t
|
||||
mmap.Close()
|
||||
temp_file_map[image_id] = &temp_resource{mmap: mmap}
|
||||
gc.SetTransmission(GRT_transmission_sharedmem)
|
||||
gc.WriteWithPayloadToLoop(lp, utils.UnsafeStringToBytes(mmap.Name()))
|
||||
_ = gc.WriteWithPayloadToLoop(lp, utils.UnsafeStringToBytes(mmap.Name()))
|
||||
}
|
||||
|
||||
func transmit_by_file(lp *loop.Loop, image_id uint32, temp_file_map map[uint32]*temp_resource, frame *images.ImageFrame, gc *GraphicsCommand) {
|
||||
@@ -365,7 +366,7 @@ func transmit_by_file(lp *loop.Loop, image_id uint32, temp_file_map map[uint32]*
|
||||
return
|
||||
}
|
||||
gc.SetTransmission(GRT_transmission_tempfile)
|
||||
gc.WriteWithPayloadToLoop(lp, utils.UnsafeStringToBytes(f.Name()))
|
||||
_ = gc.WriteWithPayloadToLoop(lp, utils.UnsafeStringToBytes(f.Name()))
|
||||
}
|
||||
|
||||
func (self *ImageCollection) transmit_rendering(lp *loop.Loop, r *rendering) {
|
||||
@@ -411,17 +412,17 @@ func (self *ImageCollection) transmit_rendering(lp *loop.Loop, r *rendering) {
|
||||
c.SetTargetFrame(uint64(frame.Number))
|
||||
c.SetGap(int32(frame.Delay_ms))
|
||||
c.SetNumberOfLoops(1)
|
||||
c.WriteWithPayloadToLoop(lp, nil)
|
||||
_ = c.WriteWithPayloadToLoop(lp, nil)
|
||||
case 1:
|
||||
c := frame_control_cmd
|
||||
c.SetAnimationControl(2) // set animation to loading mode
|
||||
c.WriteWithPayloadToLoop(lp, nil)
|
||||
_ = c.WriteWithPayloadToLoop(lp, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_animated {
|
||||
c := frame_control_cmd
|
||||
c.SetAnimationControl(3) // set animation to normal mode
|
||||
c.WriteWithPayloadToLoop(lp, nil)
|
||||
_ = c.WriteWithPayloadToLoop(lp, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ func compress_with_zlib(data []byte) []byte {
|
||||
var b bytes.Buffer
|
||||
b.Grow(len(data) + 128)
|
||||
w := zlib.NewWriter(&b)
|
||||
w.Write(data)
|
||||
_, _ = w.Write(data)
|
||||
w.Close()
|
||||
return b.Bytes()
|
||||
}
|
||||
@@ -254,7 +254,7 @@ func compress_with_zlib(data []byte) []byte {
|
||||
func (self *GraphicsCommand) AsAPC(payload []byte) string {
|
||||
buf := strings.Builder{}
|
||||
buf.Grow(1024)
|
||||
self.WriteWithPayloadTo(&buf, payload)
|
||||
_ = self.WriteWithPayloadTo(&buf, payload)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
@@ -417,7 +417,7 @@ func GraphicsCommandFromAPCPayload(raw []byte) *GraphicsCommand {
|
||||
var gc GraphicsCommand
|
||||
|
||||
add_key := func(pos int) {
|
||||
gc.SetString(current_key, utils.UnsafeBytesToString(raw[value_start_at:pos]))
|
||||
_ = gc.SetString(current_key, utils.UnsafeBytesToString(raw[value_start_at:pos]))
|
||||
}
|
||||
|
||||
for pos, ch := range raw {
|
||||
|
||||
@@ -71,7 +71,9 @@ func TestGraphicsCommandSerialization(t *testing.T) {
|
||||
b.Write(decoded)
|
||||
r, _ := zlib.NewReader(&b)
|
||||
o := bytes.Buffer{}
|
||||
io.Copy(&o, r)
|
||||
if _, err = io.Copy(&o, r); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r.Close()
|
||||
decoded = o.Bytes()
|
||||
}
|
||||
@@ -93,7 +95,7 @@ func TestGraphicsCommandSerialization(t *testing.T) {
|
||||
|
||||
test_chunked_payload([]byte("abcd"))
|
||||
data := make([]byte, 8111)
|
||||
rand.Read(data)
|
||||
_, _ = rand.Read(data)
|
||||
test_chunked_payload(data)
|
||||
test_chunked_payload([]byte(strings.Repeat("a", 8007)))
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ func (self *Loop) Run() (err error) {
|
||||
defer term.RestoreAndClose()
|
||||
fmt.Println("Press any key to exit.\r")
|
||||
buf := make([]byte, 16)
|
||||
term.Read(buf)
|
||||
_, _ = term.Read(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,10 +426,10 @@ type DefaultColor int
|
||||
|
||||
const (
|
||||
BACKGROUND DefaultColor = 11
|
||||
FOREGROUND = 10
|
||||
CURSOR = 12
|
||||
SELECTION_BG = 17
|
||||
SELECTION_FG = 19
|
||||
FOREGROUND DefaultColor = 10
|
||||
CURSOR DefaultColor = 12
|
||||
SELECTION_BG DefaultColor = 17
|
||||
SELECTION_FG DefaultColor = 19
|
||||
)
|
||||
|
||||
func (self *Loop) SetDefaultColor(which DefaultColor, val style.RGBA) {
|
||||
|
||||
@@ -128,7 +128,9 @@ func decode_sgr_mouse(text string, screen_size ScreenSize) *MouseEvent {
|
||||
if len(parts[2]) < 1 {
|
||||
return nil
|
||||
}
|
||||
ans.Pixel.Y, err = strconv.Atoi(parts[2])
|
||||
if ans.Pixel.Y, err = strconv.Atoi(parts[2]); err != nil {
|
||||
return nil
|
||||
}
|
||||
if last_letter == 'm' {
|
||||
ans.Event_type = MOUSE_RELEASE
|
||||
} else if cb&MOTION_INDICATOR != 0 {
|
||||
|
||||
@@ -42,7 +42,7 @@ func is_temporary_error(err error) bool {
|
||||
}
|
||||
|
||||
func kill_self(sig unix.Signal) {
|
||||
unix.Kill(os.Getpid(), sig)
|
||||
_ = unix.Kill(os.Getpid(), sig)
|
||||
// Give the signal time to be delivered
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
@@ -189,10 +189,11 @@ func (self *Loop) handle_rune(raw rune) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Loop) handle_end_of_bracketed_paste() {
|
||||
func (self *Loop) handle_end_of_bracketed_paste() error {
|
||||
if self.OnText != nil {
|
||||
self.OnText("", false, false)
|
||||
return self.OnText("", false, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Loop) on_signal(s unix.Signal) error {
|
||||
@@ -265,7 +266,7 @@ func (self *Loop) run() (err error) {
|
||||
signal.Notify(signal_channel, handled_signals...)
|
||||
defer signal.Reset(handled_signals...)
|
||||
|
||||
controlling_term, err := tty.OpenControllingTerm()
|
||||
controlling_term, err := tty.OpenControllingTerm(tty.SetRaw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -274,10 +275,6 @@ func (self *Loop) run() (err error) {
|
||||
controlling_term.RestoreAndClose()
|
||||
self.controlling_term = nil
|
||||
}()
|
||||
err = controlling_term.ApplyOperations(tty.TCSANOW, tty.SetRaw)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.keep_going = true
|
||||
self.pending_mouse_events = utils.NewRingBuffer[MouseEvent](4)
|
||||
@@ -401,7 +398,7 @@ func (self *Loop) run() (err error) {
|
||||
return err
|
||||
}
|
||||
err = controlling_term.SuspendAndRun(func() error {
|
||||
unix.Kill(os.Getpid(), unix.SIGSTOP)
|
||||
_ = unix.Kill(os.Getpid(), unix.SIGSTOP)
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
)
|
||||
|
||||
var debugprintln = tty.DebugPrintln
|
||||
var _ = debugprintln
|
||||
|
||||
type timer struct {
|
||||
interval time.Duration
|
||||
@@ -27,7 +28,7 @@ func (self *timer) update_deadline(now time.Time) {
|
||||
}
|
||||
|
||||
func (self timer) String() string {
|
||||
return fmt.Sprintf("Timer(id=%d, callback=%s, deadline=%s, repeats=%v)", self.id, utils.FunctionName(self.callback), self.deadline.Sub(time.Now()), self.repeats)
|
||||
return fmt.Sprintf("Timer(id=%d, callback=%s, deadline=%s, repeats=%v)", self.id, utils.FunctionName(self.callback), time.Until(self.deadline), self.repeats)
|
||||
}
|
||||
|
||||
func (self *Loop) add_timer(interval time.Duration, repeats bool, callback TimerCallback) (IdType, error) {
|
||||
|
||||
@@ -79,7 +79,7 @@ func (self *Loop) wait_for_write_to_complete(sentinel IdType, tty_write_channel
|
||||
|
||||
end_time := time.Now().Add(timeout)
|
||||
for num_sent < len(self.pending_writes) {
|
||||
timeout = end_time.Sub(time.Now())
|
||||
timeout = time.Until(end_time)
|
||||
if timeout <= 0 {
|
||||
return os.ErrDeadlineExceeded
|
||||
}
|
||||
@@ -104,7 +104,7 @@ func (self *Loop) wait_for_write_to_complete(sentinel IdType, tty_write_channel
|
||||
}
|
||||
}
|
||||
for {
|
||||
timeout = end_time.Sub(time.Now())
|
||||
timeout = time.Until(end_time)
|
||||
if timeout <= 0 {
|
||||
return os.ErrDeadlineExceeded
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func read_relevant_kitty_opts(path string) KittyOpts {
|
||||
return nil
|
||||
}
|
||||
cp := config.ConfigParser{LineHandler: handle_line}
|
||||
cp.ParseFiles(path)
|
||||
_ = cp.ParseFiles(path)
|
||||
if ans.Shell == "" {
|
||||
ans.Shell = kitty.KittyConfigDefaults.Shell
|
||||
}
|
||||
@@ -131,10 +131,7 @@ func get_shell_name(argv0 string) (ans string) {
|
||||
if strings.HasSuffix(strings.ToLower(ans), ".exe") {
|
||||
ans = ans[:len(ans)-4]
|
||||
}
|
||||
if strings.HasPrefix(ans, "-") {
|
||||
ans = ans[1:]
|
||||
}
|
||||
return
|
||||
return strings.TrimPrefix(ans, "-")
|
||||
}
|
||||
|
||||
func rc_modification_allowed(ksi string) bool {
|
||||
@@ -147,7 +144,7 @@ func rc_modification_allowed(ksi string) bool {
|
||||
return ksi != ""
|
||||
}
|
||||
|
||||
func RunShell(shell_cmd []string, shell_integration_env_var_val string) (err error) {
|
||||
func RunShell(shell_cmd []string, shell_integration_env_var_val, cwd string) (err error) {
|
||||
shell_name := get_shell_name(shell_cmd[0])
|
||||
var shell_env map[string]string
|
||||
if rc_modification_allowed(shell_integration_env_var_val) && shell_integration.IsSupportedShell(shell_name) {
|
||||
@@ -181,6 +178,9 @@ func RunShell(shell_cmd []string, shell_integration_env_var_val string) (err err
|
||||
env = os.Environ()
|
||||
}
|
||||
// fmt.Println(fmt.Sprintf("%s %v\n%#v", utils.FindExe(exe), shell_cmd, env))
|
||||
if cwd != "" {
|
||||
_ = os.Chdir(cwd)
|
||||
}
|
||||
return unix.Exec(utils.FindExe(exe), shell_cmd, env)
|
||||
}
|
||||
|
||||
@@ -194,16 +194,19 @@ func RunCommandRestoringTerminalToSaneStateAfter(cmd []string) {
|
||||
if err == nil {
|
||||
var state_before unix.Termios
|
||||
if term.Tcgetattr(&state_before) == nil {
|
||||
term.WriteString(loop.SAVE_PRIVATE_MODE_VALUES)
|
||||
if _, err = term.WriteString(loop.SAVE_PRIVATE_MODE_VALUES); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "failed to write to controlling terminal with error:", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
term.WriteString(strings.Join([]string{
|
||||
_, _ = term.WriteString(strings.Join([]string{
|
||||
loop.RESTORE_PRIVATE_MODE_VALUES,
|
||||
"\x1b[=u", // reset kitty keyboard protocol to legacy
|
||||
"\x1b[1 q", // blinking block cursor
|
||||
loop.DECTCEM.EscapeCodeToSet(), // cursor visible
|
||||
"\x1b]112\a", // reset cursor color
|
||||
}, ""))
|
||||
term.Tcsetattr(tty.TCSANOW, &state_before)
|
||||
_ = term.Tcsetattr(tty.TCSANOW, &state_before)
|
||||
term.Close()
|
||||
}()
|
||||
} else {
|
||||
|
||||
@@ -324,8 +324,9 @@ func parse_identify_record(ans *IdentifyRecord, raw *IdentifyOutput) (err error)
|
||||
if found {
|
||||
ans.Canvas.Left, err = strconv.Atoi(x)
|
||||
if err == nil {
|
||||
ans.Canvas.Top, err = strconv.Atoi(y)
|
||||
ok = true
|
||||
if ans.Canvas.Top, err = strconv.Atoi(y); err == nil {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,8 +341,9 @@ func parse_identify_record(ans *IdentifyRecord, raw *IdentifyOutput) (err error)
|
||||
if found {
|
||||
ans.Width, err = strconv.Atoi(w)
|
||||
if err == nil {
|
||||
ans.Height, err = strconv.Atoi(h)
|
||||
ok = true
|
||||
if ans.Height, err = strconv.Atoi(h); err == nil {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
@@ -352,8 +354,9 @@ func parse_identify_record(ans *IdentifyRecord, raw *IdentifyOutput) (err error)
|
||||
if found {
|
||||
ans.Dpi.X, err = strconv.ParseFloat(x, 64)
|
||||
if err == nil {
|
||||
ans.Dpi.Y, err = strconv.ParseFloat(y, 64)
|
||||
ok = true
|
||||
if ans.Dpi.Y, err = strconv.ParseFloat(y, 64); err == nil {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
|
||||
20
tools/utils/shm/fallocate_linux.go
Normal file
20
tools/utils/shm/fallocate_linux.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func Fallocate_simple(fd int, size int64) (err error) {
|
||||
for {
|
||||
if err = unix.Fallocate(fd, 0, 0, size); !errors.Is(err, unix.EINTR) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
16
tools/utils/shm/fallocate_other.go
Normal file
16
tools/utils/shm/fallocate_other.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
//go:build !linux
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func Fallocate_simple(fd int, size int64) (err error) {
|
||||
return errors.ErrUnsupported
|
||||
}
|
||||
@@ -92,16 +92,22 @@ func CreateTemp(pattern string, size uint64) (MMap, error) {
|
||||
return create_temp(pattern, size)
|
||||
}
|
||||
|
||||
func truncate_or_unlink(ans *os.File, size uint64) (err error) {
|
||||
for {
|
||||
err = unix.Ftruncate(int(ans.Fd()), int64(size))
|
||||
if !errors.Is(err, unix.EINTR) {
|
||||
break
|
||||
func truncate_or_unlink(ans *os.File, size uint64, unlink func(string) error) (err error) {
|
||||
fd := int(ans.Fd())
|
||||
sz := int64(size)
|
||||
if err = Fallocate_simple(fd, sz); err != nil {
|
||||
if !errors.Is(err, errors.ErrUnsupported) {
|
||||
return fmt.Errorf("fallocate() failed on fd from shm_open(%s) with size: %d with error: %w", ans.Name(), size, err)
|
||||
}
|
||||
for {
|
||||
if err = unix.Ftruncate(fd, sz); !errors.Is(err, unix.EINTR) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
ans.Close()
|
||||
os.Remove(ans.Name())
|
||||
_ = ans.Close()
|
||||
_ = unlink(ans.Name())
|
||||
return fmt.Errorf("Failed to ftruncate() SHM file %s to size: %d with error: %w", ans.Name(), size, err)
|
||||
}
|
||||
return
|
||||
@@ -152,7 +158,7 @@ func ReadWithSizeAndUnlink(name string, file_callback ...func(fs.FileInfo) error
|
||||
}
|
||||
defer func() {
|
||||
mmap.Close()
|
||||
mmap.Unlink()
|
||||
_ = mmap.Unlink()
|
||||
}()
|
||||
slice, err := ReadWithSize(mmap, 0)
|
||||
if err != nil {
|
||||
@@ -164,7 +170,10 @@ func ReadWithSizeAndUnlink(name string, file_callback ...func(fs.FileInfo) error
|
||||
}
|
||||
|
||||
func Read(self MMap, b []byte) (n int, err error) {
|
||||
pos, _ := self.Seek(0, io.SeekCurrent)
|
||||
pos, err := self.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if pos < 0 {
|
||||
pos = 0
|
||||
}
|
||||
@@ -174,7 +183,7 @@ func Read(self MMap, b []byte) (n int, err error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(b, s[pos:])
|
||||
self.Seek(int64(n), io.SeekCurrent)
|
||||
_, err = self.Seek(int64(n), io.SeekCurrent)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -191,7 +200,9 @@ func Write(self MMap, b []byte) (n int, err error) {
|
||||
return 0, io.ErrShortWrite
|
||||
}
|
||||
n = copy(s[pos:], b)
|
||||
self.Seek(int64(n), io.SeekCurrent)
|
||||
if _, err = self.Seek(int64(n), io.SeekCurrent); err != nil {
|
||||
return n, err
|
||||
}
|
||||
if n < len(b) {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
@@ -220,7 +231,9 @@ func test_integration_with_python(args []string) (rc int, err error) {
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
WriteWithSize(mmap, data, 0)
|
||||
if err = WriteWithSize(mmap, data, 0); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
mmap.Close()
|
||||
fmt.Println(mmap.Name())
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ type file_based_mmap struct {
|
||||
|
||||
func file_mmap(f *os.File, size uint64, access AccessFlags, truncate bool, special_name string) (MMap, error) {
|
||||
if truncate {
|
||||
err := truncate_or_unlink(f, size)
|
||||
err := truncate_or_unlink(f, size, os.Remove)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -73,15 +73,15 @@ type syscall_based_mmap struct {
|
||||
|
||||
func syscall_mmap(f *os.File, size uint64, access AccessFlags, truncate bool) (MMap, error) {
|
||||
if truncate {
|
||||
err := truncate_or_unlink(f, size)
|
||||
err := truncate_or_unlink(f, size, shm_unlink)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("truncate failed with error: %w", err)
|
||||
}
|
||||
}
|
||||
region, err := mmap(int(size), access, int(f.Fd()), 0)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
os.Remove(f.Name())
|
||||
_ = f.Close()
|
||||
_ = shm_unlink(f.Name())
|
||||
return nil, fmt.Errorf("mmap failed with error: %w", err)
|
||||
}
|
||||
return &syscall_based_mmap{f: f, region: region}, nil
|
||||
|
||||
@@ -16,7 +16,7 @@ var _ = fmt.Print
|
||||
|
||||
func TestSHM(t *testing.T) {
|
||||
data := make([]byte, 13347)
|
||||
rand.Read(data)
|
||||
_, _ = rand.Read(data)
|
||||
mm, err := CreateTemp("test-kitty-shm-", uint64(len(data)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -52,7 +52,7 @@ type EscapeCodeParser struct {
|
||||
|
||||
// Callbacks
|
||||
HandleRune func(rune) error
|
||||
HandleEndOfBracketedPaste func()
|
||||
HandleEndOfBracketedPaste func() error
|
||||
HandleCSI func([]byte) error
|
||||
HandleOSC func([]byte) error
|
||||
HandleDCS func([]byte) error
|
||||
@@ -199,7 +199,9 @@ func (self *EscapeCodeParser) dispatch_char(ch utils.UTF8State) error {
|
||||
if self.bracketed_paste_buffer[len(self.bracketed_paste_buffer)-1] == '~' {
|
||||
self.reset_state()
|
||||
if self.HandleEndOfBracketedPaste != nil {
|
||||
self.HandleEndOfBracketedPaste()
|
||||
if err := self.HandleEndOfBracketedPaste(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user