Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f66db65974 | ||
|
|
5b6038db35 | ||
|
|
9a365b0968 | ||
|
|
8a0cd73821 | ||
|
|
49b065cbe7 | ||
|
|
77f651cb5d | ||
|
|
fedde51c12 | ||
|
|
f710d6906e | ||
|
|
c9a751360e | ||
|
|
ad97315925 | ||
|
|
e8f7a2b345 | ||
|
|
5bc8d05f0a | ||
|
|
3669d4135c | ||
|
|
73b870bb87 | ||
|
|
6b4217c12a | ||
|
|
8b4fe494cf | ||
|
|
35041e7709 | ||
|
|
12cbcf1c17 | ||
|
|
26f50a451e | ||
|
|
11f3d7e215 | ||
|
|
a7d533cfe5 | ||
|
|
52ec4dc21b | ||
|
|
6f7ea49008 | ||
|
|
84b99b1b3d | ||
|
|
1022d8a0ee | ||
|
|
cd6a46730a | ||
|
|
59743fd7a1 | ||
|
|
07bd371e21 | ||
|
|
fe3b10a8fb | ||
|
|
c994bc1d89 | ||
|
|
22cffef7c8 | ||
|
|
5dfb8b1d80 | ||
|
|
82f9f002bd | ||
|
|
9f51506422 | ||
|
|
2a8295d71c | ||
|
|
7214b66aa5 | ||
|
|
a7d9c63a4e | ||
|
|
61dd52b50f | ||
|
|
a157f3c5e3 | ||
|
|
fc02246373 | ||
|
|
adb8a4676c | ||
|
|
f78d05a57f | ||
|
|
301d34fecf | ||
|
|
0848816644 | ||
|
|
ba7f0132f2 | ||
|
|
561fe81d00 | ||
|
|
2db83ecf1e | ||
|
|
1b8c6b23e1 | ||
|
|
b6622b9458 |
20
.travis.yml
20
.travis.yml
@@ -150,9 +150,17 @@ before_script:
|
|||||||
- echo LD_LIBRARY_PATH=$LD_LIBRARY_PATH PKG_CONFIG_PATH=$PKG_CONFIG_PATH
|
- echo LD_LIBRARY_PATH=$LD_LIBRARY_PATH PKG_CONFIG_PATH=$PKG_CONFIG_PATH
|
||||||
- $PYTHON setup.py build --debug --verbose $SANITIZE_ARG;
|
- $PYTHON setup.py build --debug --verbose $SANITIZE_ARG;
|
||||||
|
|
||||||
script:
|
script: |
|
||||||
- if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
|
set -e
|
||||||
- if [[ -z $SANITIZE_ARG ]]; then $PYTHON test.py; else ./asan-launcher test.py; fi
|
|
||||||
- if [[ "$RUN_FLAKE" == "1" ]]; then flake8 --count .; fi
|
if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
|
||||||
- if [[ "$BUILD_PKG" == "linux-package" ]]; then make FAIL_WARN=-W man && make FAIL_WARN=-W html; fi
|
|
||||||
- if [[ ! -z "$BUILD_PKG" ]]; then $PYTHON setup.py $BUILD_PKG; fi
|
if [[ -z "$SANITIZE_ARG" ]]; then $PYTHON test.py; else ./asan-launcher test.py; fi
|
||||||
|
|
||||||
|
if [[ "$RUN_FLAKE" == "1" ]]; then flake8 --count .; fi
|
||||||
|
|
||||||
|
if [[ "$BUILD_PKG" == "linux-package" ]]; then make FAIL_WARN=-W man && make FAIL_WARN=-W html; fi
|
||||||
|
|
||||||
|
if [[ ! -z "$BUILD_PKG" ]]; then $PYTHON setup.py $BUILD_PKG; fi
|
||||||
|
|
||||||
|
set +e
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -39,4 +39,7 @@ man:
|
|||||||
html:
|
html:
|
||||||
$(MAKE) FAIL_WARN=$(FAIL_WARN) -C docs html
|
$(MAKE) FAIL_WARN=$(FAIL_WARN) -C docs html
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
$(MAKE) FAIL_WARN=$(FAIL_WARN) -C docs linkcheck
|
||||||
|
|
||||||
docs: man html
|
docs: man html
|
||||||
|
|||||||
@@ -82,8 +82,9 @@ without needing to install all of kitty.
|
|||||||
.. note::
|
.. note::
|
||||||
You need a couple of extra dependencies to build linux-package.
|
You need a couple of extra dependencies to build linux-package.
|
||||||
:file:`tic` to compile terminfo files, usually found in the
|
:file:`tic` to compile terminfo files, usually found in the
|
||||||
development package of :file:`ncurses`. Also, :file:`sphinx-build`
|
development package of :file:`ncurses`. Also, if you are building form
|
||||||
from the `Sphinx documentation generator
|
a git checkout instead of the released source code tarball, you will
|
||||||
|
need :file:`sphinx-build` from the `Sphinx documentation generator
|
||||||
<http://www.sphinx-doc.org/>`_.
|
<http://www.sphinx-doc.org/>`_.
|
||||||
|
|
||||||
This applies to creating packages for kitty for macOS package managers such as
|
This applies to creating packages for kitty for macOS package managers such as
|
||||||
|
|||||||
@@ -3,6 +3,34 @@ Changelog
|
|||||||
|
|
||||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||||
|
|
||||||
|
0.11.1 [2018-06-17]
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
- diff kitten: Implement searching for text in the diff (:iss:`574`)
|
||||||
|
|
||||||
|
- Add an option :opt:`startup_session` to :file:`kitty.conf` to specify a
|
||||||
|
default startup session (:iss:`641`)
|
||||||
|
|
||||||
|
- Add a command line option :option:`kitty --wait-for-single-instance-window-close`
|
||||||
|
to make :option:`kitty --single-instance` wait for the closing of the newly opened
|
||||||
|
window before quitting (:iss:`630`)
|
||||||
|
|
||||||
|
- diff kitten: Allow theming the selection background/foreground as well
|
||||||
|
|
||||||
|
- diff kitten: Display CRLF line endings using the unicode return symbol
|
||||||
|
instead of <d> as it is less intrusive (:iss:`638`)
|
||||||
|
|
||||||
|
- diff kitten: Fix default foreground/background colors not being restored when
|
||||||
|
kitten quits (:iss:`637`)
|
||||||
|
|
||||||
|
- Fix :option:`kitty @ set-colors --all` not working when more than one window
|
||||||
|
present (:iss:`632`)
|
||||||
|
|
||||||
|
- Fix a regression that broke the legacy increase/decrease_font_size actions
|
||||||
|
|
||||||
|
- Clear scrollback on reset (:iss:`631`)
|
||||||
|
|
||||||
|
|
||||||
0.11.0 [2018-06-12]
|
0.11.0 [2018-06-12]
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
|
|||||||
21
docs/conf.py
21
docs/conf.py
@@ -115,6 +115,9 @@ html_theme_options = {
|
|||||||
'github_banner': True,
|
'github_banner': True,
|
||||||
'github_user': 'kovidgoyal',
|
'github_user': 'kovidgoyal',
|
||||||
'github_repo': 'kitty',
|
'github_repo': 'kitty',
|
||||||
|
# increase contrast of link color with text color
|
||||||
|
'link': '#00587d',
|
||||||
|
'link_hover': 'green',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -245,6 +248,7 @@ def write_cli_docs(all_kitten_names):
|
|||||||
from kitty.cmds import cli_params_for
|
from kitty.cmds import cli_params_for
|
||||||
for cmd_name in all_commands:
|
for cmd_name in all_commands:
|
||||||
func = cmap[cmd_name]
|
func = cmap[cmd_name]
|
||||||
|
p(f'.. _at_{func.name}:\n')
|
||||||
p('kitty @', func.name + '\n' + '-' * 120)
|
p('kitty @', func.name + '\n' + '-' * 120)
|
||||||
p('.. program::', 'kitty @', func.name)
|
p('.. program::', 'kitty @', func.name)
|
||||||
p('\n\n' + as_rst(*cli_params_for(func)))
|
p('\n\n' + as_rst(*cli_params_for(func)))
|
||||||
@@ -496,35 +500,30 @@ def write_conf_docs(app, all_kitten_names):
|
|||||||
sc_role.warn_dangling = True
|
sc_role.warn_dangling = True
|
||||||
sc_role.process_link = process_shortcut_link
|
sc_role.process_link = process_shortcut_link
|
||||||
|
|
||||||
def generate(all_options, name='kitty'):
|
def generate_default_config(all_options, name):
|
||||||
from kitty.conf.definition import as_conf_file
|
from kitty.conf.definition import as_conf_file
|
||||||
from textwrap import indent
|
|
||||||
with open(f'generated/conf-{name}.rst', 'w', encoding='utf-8') as f:
|
with open(f'generated/conf-{name}.rst', 'w', encoding='utf-8') as f:
|
||||||
print('.. highlight:: conf\n', file=f)
|
print('.. highlight:: conf\n', file=f)
|
||||||
f.write(render_conf(name, all_options.values()))
|
f.write(render_conf(name, all_options.values()))
|
||||||
|
|
||||||
with open(f'generated/conf-{name}-literal.rst', 'w', encoding='utf-8') as f:
|
conf_name = re.sub(r'^kitten-', '', name) + '.conf'
|
||||||
print('.. code-block:: conf\n', file=f)
|
with open(f'generated/conf/{conf_name}', 'w', encoding='utf-8') as f:
|
||||||
text = '\n'.join(as_conf_file(all_options.values()))
|
text = '\n'.join(as_conf_file(all_options.values()))
|
||||||
text = indent(text, ' ', lambda l: True)
|
|
||||||
print(text, file=f)
|
print(text, file=f)
|
||||||
|
|
||||||
from kitty.config_data import all_options
|
from kitty.config_data import all_options
|
||||||
generate(all_options)
|
generate_default_config(all_options, 'kitty')
|
||||||
|
|
||||||
from kittens.runner import get_kitten_conf_docs
|
from kittens.runner import get_kitten_conf_docs
|
||||||
for kitten in all_kitten_names:
|
for kitten in all_kitten_names:
|
||||||
all_options = get_kitten_conf_docs(kitten)
|
all_options = get_kitten_conf_docs(kitten)
|
||||||
if all_options:
|
if all_options:
|
||||||
generate(all_options, f'kitten-{kitten}')
|
generate_default_config(all_options, f'kitten-{kitten}')
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
try:
|
os.makedirs('generated/conf', exist_ok=True)
|
||||||
os.mkdir('generated')
|
|
||||||
except FileExistsError:
|
|
||||||
pass
|
|
||||||
from kittens.runner import all_kitten_names
|
from kittens.runner import all_kitten_names
|
||||||
all_kitten_names = all_kitten_names()
|
all_kitten_names = all_kitten_names()
|
||||||
write_cli_docs(all_kitten_names)
|
write_cli_docs(all_kitten_names)
|
||||||
|
|||||||
@@ -35,6 +35,6 @@ expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if
|
|||||||
Sample kitty.conf
|
Sample kitty.conf
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Below is a sample :file:`kitty.conf` with all default settings.
|
You can download a sample :file:`kitty.conf` file with all default settings and
|
||||||
|
comments describing each setting by clicking: :download:`sample kitty.conf
|
||||||
.. include:: /generated/conf-kitty-literal.rst
|
</generated/conf/kitty.conf>`.
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ How do I change the colors in a running kitty instance?
|
|||||||
You can either use the
|
You can either use the
|
||||||
`OSC terminal escape codes <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands>`_
|
`OSC terminal escape codes <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands>`_
|
||||||
to set colors or you can enable :doc:`remote control <remote-control>`
|
to set colors or you can enable :doc:`remote control <remote-control>`
|
||||||
for |kitty| and use ``kitty @ set-colors --help``.
|
for |kitty| and use :ref:`at_set-colors`.
|
||||||
|
|
||||||
|
|
||||||
How do I specify command line options for kitty on macOS?
|
How do I specify command line options for kitty on macOS?
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
:tocdepth: 2
|
:tocdepth: 2
|
||||||
|
|
||||||
==========================================================
|
==========================================================
|
||||||
kitty - the fast, featureful, GPU based, terminal emulator
|
kitty - the fast, featureful, GPU based terminal emulator
|
||||||
==========================================================
|
==========================================================
|
||||||
|
|
||||||
.. container:: major-features
|
.. container:: major-features
|
||||||
@@ -250,14 +250,22 @@ Some prominent kittens:
|
|||||||
Copy/paste to the clipboard from shell scripts, even over SSH.
|
Copy/paste to the clipboard from shell scripts, even over SSH.
|
||||||
|
|
||||||
|
|
||||||
|
Configuring kitty
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
|kitty| is highly configurable, everything from keyboard shortcuts to
|
||||||
|
painting frames-per-second. For details and a sample :file:`kitty.conf`,
|
||||||
|
see the :doc:`configuration docs <conf>`.
|
||||||
|
|
||||||
.. _sessions:
|
.. _sessions:
|
||||||
|
|
||||||
Startup Sessions
|
Startup Sessions
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
You can control the tabs, window layout, working directory, startup
|
You can control the tabs, window layout, working directory, startup programs,
|
||||||
programs, etc. by creating a "session" file and using the :option:`kitty --session`
|
etc. by creating a "session" file and using the :option:`kitty --session`
|
||||||
command line flag. For example:
|
command line flag or the :opt:`startup_session` option in :file:`kitty.conf`.
|
||||||
|
For example:
|
||||||
|
|
||||||
.. code-block:: session
|
.. code-block:: session
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ Major Features
|
|||||||
Installation
|
Installation
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
Simply install :ref:`kitty <quickstart>`. You also need
|
Simply :ref:`install kitty <quickstart>`. You also need
|
||||||
to have either the `git <https://git-scm.com/>`_ program or the ``diff`` program
|
to have either the `git <https://git-scm.com/>`_ program or the ``diff`` program
|
||||||
installed. Additionally, for syntax highlighting to work,
|
installed. Additionally, for syntax highlighting to work,
|
||||||
`pygments <http://pygments.org/>`_ must be installed (note that pygments is
|
`pygments <http://pygments.org/>`_ must be installed (note that pygments is
|
||||||
@@ -67,37 +67,29 @@ Keyboard controls
|
|||||||
========================= ===========================
|
========================= ===========================
|
||||||
Action Shortcut
|
Action Shortcut
|
||||||
========================= ===========================
|
========================= ===========================
|
||||||
Quit ``q, Ctrl+c``
|
Quit :kbd:`q, Ctrl+c, Esc`
|
||||||
Scroll line up ``k, up``
|
Scroll line up :kbd:`k, up`
|
||||||
Scroll line down ``j, down``
|
Scroll line down :kbd:`j, down`
|
||||||
Scroll page up ``PgUp``
|
Scroll page up :kbd:`PgUp`
|
||||||
Scroll page down ``PgDn``
|
Scroll page down :kbd:`PgDn`
|
||||||
Scroll to top ``Home``
|
Scroll to top :kbd:`Home`
|
||||||
Scroll to bottom ``End``
|
Scroll to bottom :kbd:`End`
|
||||||
Scroll to next page ``Space, PgDn``
|
Scroll to next page :kbd:`Space, PgDn`
|
||||||
Scroll to previous page ``PgUp``
|
Scroll to previous page :kbd:`PgUp`
|
||||||
Scroll to next change ``n``
|
Scroll to next change :kbd:`n`
|
||||||
Scroll to previous change ``p``
|
Scroll to previous change :kbd:`p`
|
||||||
Increase lines of context ``+``
|
Increase lines of context :kbd:`+`
|
||||||
Decrease lines of context ``-``
|
Decrease lines of context :kbd:`-`
|
||||||
All lines of context ``a``
|
All lines of context :kbd:`a`
|
||||||
Restore default context ``=``
|
Restore default context :kbd:`=`
|
||||||
|
Search forwards :kbd:`/`
|
||||||
|
Search backwards :kbd:`?`
|
||||||
|
Clear search :kbd:`Esc`
|
||||||
|
Scroll to next match :kbd:`>, .`
|
||||||
|
Scroll to previous match :kbd:`<, ,`
|
||||||
========================= ===========================
|
========================= ===========================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
You can configure the colors used, keyboard shortcuts, the diff implementation,
|
|
||||||
the default lines of context, etc. by creating a :file:`diff.conf` file in
|
|
||||||
your :ref:`kitty config folder <confloc>`. See below for the supported
|
|
||||||
configuration directives.
|
|
||||||
|
|
||||||
|
|
||||||
.. include:: /generated/conf-kitten-diff.rst
|
|
||||||
|
|
||||||
|
|
||||||
Integrating with git
|
Integrating with git
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
@@ -122,11 +114,6 @@ Now to use kitty-diff to view git diffs, you can simply do::
|
|||||||
|
|
||||||
Once again, creating an alias for this command is useful.
|
Once again, creating an alias for this command is useful.
|
||||||
|
|
||||||
Command Line Interface
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
.. include:: /generated/cli-kitten-diff.rst
|
|
||||||
|
|
||||||
|
|
||||||
Why does this work only in kitty?
|
Why does this work only in kitty?
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
@@ -142,9 +129,29 @@ And fundamentally, it's kitty only because I wrote it for myself, and I am
|
|||||||
highly unlikely to use any other terminals :)
|
highly unlikely to use any other terminals :)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
You can configure the colors used, keyboard shortcuts, the diff implementation,
|
||||||
|
the default lines of context, etc. by creating a :file:`diff.conf` file in
|
||||||
|
your :ref:`kitty config folder <confloc>`. See below for the supported
|
||||||
|
configuration directives.
|
||||||
|
|
||||||
|
|
||||||
|
.. include:: /generated/conf-kitten-diff.rst
|
||||||
|
|
||||||
|
|
||||||
|
Command Line Interface
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. include:: /generated/cli-kitten-diff.rst
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Sample diff.conf
|
Sample diff.conf
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Below is a sample :file:`diff.conf` with all default settings.
|
You can download a sample :file:`diff.conf` file with all default settings and
|
||||||
|
comments describing each setting by clicking: :download:`sample diff.conf
|
||||||
.. include:: /generated/conf-kitten-diff-literal.rst
|
</generated/conf/diff.conf>`.
|
||||||
|
|||||||
@@ -43,3 +43,17 @@ konsole 29 - 31%
|
|||||||
As you can see, |kitty| uses much less CPU than all terminals, except xterm, but
|
As you can see, |kitty| uses much less CPU than all terminals, except xterm, but
|
||||||
its scrolling "smoothness" is much better than that of xterm (at least to my,
|
its scrolling "smoothness" is much better than that of xterm (at least to my,
|
||||||
admittedly biased, eyes).
|
admittedly biased, eyes).
|
||||||
|
|
||||||
|
|
||||||
|
.. _perf-cat:
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Some people have asked why kitty does not perform better than terminal XXX in
|
||||||
|
the test of sinking large amounts of data, such as catting a large text
|
||||||
|
file. The answer is because this is not a goal for kitty. kitty
|
||||||
|
deliberately throttles input parsing and output rendering to minimize
|
||||||
|
resource usage while still being able to sink output faster than any real
|
||||||
|
world program can produce it. Reducing CPU usage, and hence battery drain
|
||||||
|
while achieving instant response times and smooth scrolling to a human eye
|
||||||
|
is a far more important goal.
|
||||||
|
|||||||
@@ -165,3 +165,22 @@ For example, to set the background color to blue in a
|
|||||||
rectangular region of the screen from (3, 4) to (10, 11), you use::
|
rectangular region of the screen from (3, 4) to (10, 11), you use::
|
||||||
|
|
||||||
<ESC>[2*x<ESC>[4;3;11;10;44$r<ESC>[*x
|
<ESC>[2*x<ESC>[4;3;11;10;44$r<ESC>[*x
|
||||||
|
|
||||||
|
|
||||||
|
Saving and restoring the default foreground/background/selection/cursor colors
|
||||||
|
---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
It is often useful for a full screen application with its own color themes
|
||||||
|
to set the default foreground, background, selection and cursor colors. This
|
||||||
|
allows for various performance optimizations when drawing the screen. The
|
||||||
|
problem is that if the user previously used the escape codes to change these
|
||||||
|
colors herself, then running the full screen application will lose her
|
||||||
|
changes even after it exits. To avoid this, kitty introduces a new pair of
|
||||||
|
*OSC* escape codes to push and pop the current color values from a stack::
|
||||||
|
|
||||||
|
<ESC>]30001<ESC>\ # push onto stack
|
||||||
|
<ESC>]30101<ESC>\ # pop from stack
|
||||||
|
|
||||||
|
These escape codes save/restore the so called *dynamic colors*, default
|
||||||
|
background, default foreground, selection background, selection foreground and
|
||||||
|
cursor color.
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ from urllib.request import urlopen
|
|||||||
|
|
||||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
non_characters = frozenset(range(0xfffe, 0x10ffff, 0x10000))
|
||||||
|
non_characters |= frozenset(range(0xffff, 0x10ffff + 1, 0x10000))
|
||||||
|
non_characters |= frozenset(range(0xfdd0, 0xfdf0))
|
||||||
|
if len(non_characters) != 66:
|
||||||
|
raise SystemExit('non_characters table incorrect')
|
||||||
|
|
||||||
|
|
||||||
def get_data(fname, folder='UCD'):
|
def get_data(fname, folder='UCD'):
|
||||||
url = f'https://www.unicode.org/Public/{folder}/latest/{fname}'
|
url = f'https://www.unicode.org/Public/{folder}/latest/{fname}'
|
||||||
@@ -189,11 +195,12 @@ def gen_emoji():
|
|||||||
p('\treturn false;\n}')
|
p('\treturn false;\n}')
|
||||||
|
|
||||||
|
|
||||||
def category_test(name, p, classes, comment, static=False):
|
def category_test(name, p, classes, comment, static=False, extra_chars=frozenset()):
|
||||||
static = 'static inline ' if static else ''
|
static = 'static inline ' if static else ''
|
||||||
chars = set()
|
chars = set()
|
||||||
for c in classes:
|
for c in classes:
|
||||||
chars |= class_maps[c]
|
chars |= class_maps[c]
|
||||||
|
chars |= extra_chars
|
||||||
p(f'{static}bool\n{name}(char_type code) {{')
|
p(f'{static}bool\n{name}(char_type code) {{')
|
||||||
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
||||||
p('\tswitch(code) {')
|
p('\tswitch(code) {')
|
||||||
@@ -245,7 +252,7 @@ def gen_ucd():
|
|||||||
with create_header('kitty/unicode-data.c') as p:
|
with create_header('kitty/unicode-data.c') as p:
|
||||||
p('#include "unicode-data.h"')
|
p('#include "unicode-data.h"')
|
||||||
category_test('is_combining_char', p, {c for c in class_maps if c.startswith('M')}, 'M category (marks)')
|
category_test('is_combining_char', p, {c for c in class_maps if c.startswith('M')}, 'M category (marks)')
|
||||||
category_test('is_ignored_char', p, 'Cc Cf Cs'.split(), 'Control characters (Cc Cf Cs)')
|
category_test('is_ignored_char', p, 'Cc Cf Cs'.split(), 'Control characters and non-characters', extra_chars=non_characters)
|
||||||
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
|
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
|
||||||
category_test('is_CZ_category', p, cz, 'C and Z categories')
|
category_test('is_CZ_category', p, cz, 'C and Z categories')
|
||||||
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
|
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
|
||||||
@@ -258,7 +265,7 @@ def gen_ucd():
|
|||||||
p('combining_type mark_for_codepoint(char_type c) {')
|
p('combining_type mark_for_codepoint(char_type c) {')
|
||||||
rmap = codepoint_to_mark_map(p, mark_map)
|
rmap = codepoint_to_mark_map(p, mark_map)
|
||||||
p('}\n')
|
p('}\n')
|
||||||
if rmap[0xfe0e] != 1275:
|
if rmap[0xfe0e] != 1280:
|
||||||
raise ValueError('The mark for 0xfe0e has changed, you have to update VS15 to {} and VS16 to {} in unicode-data.h'.format(
|
raise ValueError('The mark for 0xfe0e has changed, you have to update VS15 to {} and VS16 to {} in unicode-data.h'.format(
|
||||||
rmap[0xfe0e], rmap[0xfe0f]
|
rmap[0xfe0e], rmap[0xfe0f]
|
||||||
))
|
))
|
||||||
@@ -393,7 +400,7 @@ def gen_wcwidth():
|
|||||||
add(p, 'East Asian double width', doublewidth, 2)
|
add(p, 'East Asian double width', doublewidth, 2)
|
||||||
add(p, 'Emoji Presentation', emoji_categories['Emoji_Presentation'], 2)
|
add(p, 'Emoji Presentation', emoji_categories['Emoji_Presentation'], 2)
|
||||||
|
|
||||||
add(p, 'Not assigned in the unicode character database', not_assigned, -1)
|
add(p, 'Not assigned in the unicode character database', not_assigned, -4)
|
||||||
|
|
||||||
p('\t\tdefault: return 1;')
|
p('\t\tdefault: return 1;')
|
||||||
p('\t}')
|
p('\t}')
|
||||||
|
|||||||
@@ -116,12 +116,9 @@ def collect_files(collection, left, right):
|
|||||||
sanitize_pat = re.compile('[\x00-\x09\x0b-\x1f\x7f\x80-\x9f]')
|
sanitize_pat = re.compile('[\x00-\x09\x0b-\x1f\x7f\x80-\x9f]')
|
||||||
|
|
||||||
|
|
||||||
def sanitize_sub(m):
|
|
||||||
return '<{:x}>'.format(ord(m.group()[0]))
|
|
||||||
|
|
||||||
|
|
||||||
def sanitize(text):
|
def sanitize(text):
|
||||||
return sanitize_pat.sub(sanitize_sub, text)
|
ntext = text.replace('\r\n', '⏎\n')
|
||||||
|
return sanitize_pat.sub('░', ntext)
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1024)
|
@lru_cache(maxsize=1024)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ def parse_scroll_by(func, rest):
|
|||||||
@func_with_args('scroll_to')
|
@func_with_args('scroll_to')
|
||||||
def parse_scroll_to(func, rest):
|
def parse_scroll_to(func, rest):
|
||||||
rest = rest.lower()
|
rest = rest.lower()
|
||||||
if rest not in {'start', 'end', 'next-change', 'prev-change', 'next-page', 'prev-page'}:
|
if rest not in {'start', 'end', 'next-change', 'prev-change', 'next-page', 'prev-page', 'next-match', 'prev-match'}:
|
||||||
rest = 'start'
|
rest = 'start'
|
||||||
return func, rest
|
return func, rest
|
||||||
|
|
||||||
@@ -69,6 +69,14 @@ def parse_change_context(func, rest):
|
|||||||
return func, amount
|
return func, amount
|
||||||
|
|
||||||
|
|
||||||
|
@func_with_args('start_search')
|
||||||
|
def parse_start_search(func, rest):
|
||||||
|
rest = rest.lower().split()
|
||||||
|
is_regex = rest and rest[0] == 'regex'
|
||||||
|
is_backward = len(rest) > 1 and rest[1] == 'backward'
|
||||||
|
return func, (is_regex, is_backward)
|
||||||
|
|
||||||
|
|
||||||
def special_handling(key, val, ans):
|
def special_handling(key, val, ans):
|
||||||
if key == 'map':
|
if key == 'map':
|
||||||
action, *key_def = parse_kittens_key(val, args_funcs)
|
action, *key_def = parse_kittens_key(val, args_funcs)
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ c('filler_bg', '#fafbfc', long_text=_('Filler (empty) line background'))
|
|||||||
c('hunk_margin_bg', '#dbedff', long_text=_('Hunk header colors'))
|
c('hunk_margin_bg', '#dbedff', long_text=_('Hunk header colors'))
|
||||||
c('hunk_bg', '#f1f8ff')
|
c('hunk_bg', '#f1f8ff')
|
||||||
|
|
||||||
|
c('search_bg', '#444', long_text=_('Highlighting'))
|
||||||
|
c('search_fg', 'white')
|
||||||
|
c('select_bg', '#B4D5FE')
|
||||||
|
c('select_fg', 'black')
|
||||||
|
|
||||||
g('shortcuts')
|
g('shortcuts')
|
||||||
k('quit', 'q', 'quit', _('Quit'))
|
k('quit', 'q', 'quit', _('Quit'))
|
||||||
@@ -108,4 +112,13 @@ k('default_context', '=', 'change_context default', _('Show default context'))
|
|||||||
k('increase_context', '+', 'change_context 5', _('Increase context'))
|
k('increase_context', '+', 'change_context 5', _('Increase context'))
|
||||||
k('decrease_context', '-', 'change_context -5', _('Decrease context'))
|
k('decrease_context', '-', 'change_context -5', _('Decrease context'))
|
||||||
|
|
||||||
|
k('search_forward', '/', 'start_search regex forward', _('Search forward'))
|
||||||
|
k('search_backward', '?', 'start_search regex backward', _('Search backward'))
|
||||||
|
k('next_match', '.', 'scroll_to next-match', _('Scroll to next search match'))
|
||||||
|
k('prev_match', ',', 'scroll_to prev-match', _('Scroll to previous search match'))
|
||||||
|
k('next_match', '>', 'scroll_to next-match', _('Scroll to next search match'))
|
||||||
|
k('prev_match', '<', 'scroll_to prev-match', _('Scroll to previous search match'))
|
||||||
|
k('search_forward_simple', 'f', 'start_search substring forward', _('Search forward (no regex)'))
|
||||||
|
k('search_backward_simple', 'b', 'start_search substring backward', _('Search backward (no regex)'))
|
||||||
|
|
||||||
type_map = {o.name: o.option_type for o in all_options.values() if hasattr(o, 'option_type')}
|
type_map = {o.name: o.option_type for o in all_options.values() if hasattr(o, 'option_type')}
|
||||||
|
|||||||
@@ -9,20 +9,24 @@ from collections import defaultdict
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
|
||||||
from kitty.cli import CONFIG_HELP, appname, parse_args
|
from kitty.cli import CONFIG_HELP, parse_args
|
||||||
|
from kitty.constants import appname
|
||||||
from kitty.fast_data_types import wcswidth
|
from kitty.fast_data_types import wcswidth
|
||||||
from kitty.key_encoding import RELEASE
|
from kitty.key_encoding import ESCAPE, RELEASE, enter_key
|
||||||
|
|
||||||
from ..tui.handler import Handler
|
from ..tui.handler import Handler
|
||||||
from ..tui.images import ImageManager
|
from ..tui.images import ImageManager
|
||||||
|
from ..tui.line_edit import LineEdit
|
||||||
from ..tui.loop import Loop
|
from ..tui.loop import Loop
|
||||||
from ..tui.operations import styled
|
from ..tui.operations import styled
|
||||||
from .collect import (
|
from .collect import (
|
||||||
create_collection, data_for_path, lines_for_path, set_highlight_data
|
create_collection, data_for_path, lines_for_path, sanitize,
|
||||||
|
set_highlight_data
|
||||||
)
|
)
|
||||||
from .config import init_config
|
from .config import init_config
|
||||||
from .patch import Differ, set_diff_command
|
from .patch import Differ, set_diff_command
|
||||||
from .render import ImageSupportWarning, LineRef, render_diff
|
from .render import ImageSupportWarning, LineRef, render_diff
|
||||||
|
from .search import BadRegex, Search
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .highlight import initialize_highlighter, highlight_collection
|
from .highlight import initialize_highlighter, highlight_collection
|
||||||
@@ -30,7 +34,7 @@ except ImportError:
|
|||||||
initialize_highlighter = None
|
initialize_highlighter = None
|
||||||
|
|
||||||
|
|
||||||
INITIALIZING, COLLECTED, DIFFED = range(3)
|
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
|
||||||
|
|
||||||
|
|
||||||
def generate_diff(collection, context):
|
def generate_diff(collection, context):
|
||||||
@@ -51,6 +55,10 @@ class DiffHandler(Handler):
|
|||||||
|
|
||||||
def __init__(self, args, opts, left, right):
|
def __init__(self, args, opts, left, right):
|
||||||
self.state = INITIALIZING
|
self.state = INITIALIZING
|
||||||
|
self.message = ''
|
||||||
|
self.current_search_is_regex = True
|
||||||
|
self.current_search = None
|
||||||
|
self.line_edit = LineEdit()
|
||||||
self.opts = opts
|
self.opts = opts
|
||||||
self.left, self.right = left, right
|
self.left, self.right = left, right
|
||||||
self.report_traceback_on_exit = None
|
self.report_traceback_on_exit = None
|
||||||
@@ -76,6 +84,8 @@ class DiffHandler(Handler):
|
|||||||
where = args[0]
|
where = args[0]
|
||||||
if 'change' in where:
|
if 'change' in where:
|
||||||
return self.scroll_to_next_change(backwards='prev' in where)
|
return self.scroll_to_next_change(backwards='prev' in where)
|
||||||
|
if 'match' in where:
|
||||||
|
return self.scroll_to_next_match(backwards='prev' in where)
|
||||||
if 'page' in where:
|
if 'page' in where:
|
||||||
amt = self.num_lines * (1 if 'next' in where else -1)
|
amt = self.num_lines * (1 if 'next' in where else -1)
|
||||||
else:
|
else:
|
||||||
@@ -91,6 +101,9 @@ class DiffHandler(Handler):
|
|||||||
else:
|
else:
|
||||||
new_ctx += to
|
new_ctx += to
|
||||||
return self.change_context_count(new_ctx)
|
return self.change_context_count(new_ctx)
|
||||||
|
if func == 'start_search':
|
||||||
|
self.start_search(*args)
|
||||||
|
return
|
||||||
|
|
||||||
def create_collection(self):
|
def create_collection(self):
|
||||||
self.start_job('collect', create_collection, self.left, self.right)
|
self.start_job('collect', create_collection, self.left, self.right)
|
||||||
@@ -107,10 +120,13 @@ class DiffHandler(Handler):
|
|||||||
|
|
||||||
def render_diff(self):
|
def render_diff(self):
|
||||||
self.diff_lines = tuple(render_diff(self.collection, self.diff_map, self.args, self.screen_size.cols, self.image_manager))
|
self.diff_lines = tuple(render_diff(self.collection, self.diff_map, self.args, self.screen_size.cols, self.image_manager))
|
||||||
|
self.margin_size = render_diff.margin_size
|
||||||
self.ref_path_map = defaultdict(list)
|
self.ref_path_map = defaultdict(list)
|
||||||
for i, l in enumerate(self.diff_lines):
|
for i, l in enumerate(self.diff_lines):
|
||||||
self.ref_path_map[l.ref.path].append((i, l.ref))
|
self.ref_path_map[l.ref.path].append((i, l.ref))
|
||||||
self.max_scroll_pos = len(self.diff_lines) - self.num_lines
|
self.max_scroll_pos = len(self.diff_lines) - self.num_lines
|
||||||
|
if self.current_search is not None:
|
||||||
|
self.current_search(self.diff_lines, self.margin_size, self.screen_size.cols)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_position(self):
|
def current_position(self):
|
||||||
@@ -152,6 +168,18 @@ class DiffHandler(Handler):
|
|||||||
return
|
return
|
||||||
self.cmd.bell()
|
self.cmd.bell()
|
||||||
|
|
||||||
|
def scroll_to_next_match(self, backwards=False):
|
||||||
|
if self.current_search is not None:
|
||||||
|
if backwards:
|
||||||
|
r = range(self.scroll_pos - 1, -1, -1)
|
||||||
|
else:
|
||||||
|
r = range(self.scroll_pos + 1, len(self.diff_lines))
|
||||||
|
for i in r:
|
||||||
|
if i in self.current_search:
|
||||||
|
self.scroll_lines(i - self.scroll_pos)
|
||||||
|
return
|
||||||
|
self.cmd.bell()
|
||||||
|
|
||||||
def set_scrolling_region(self):
|
def set_scrolling_region(self):
|
||||||
self.cmd.set_scrolling_region(self.screen_size, 0, self.num_lines - 2)
|
self.cmd.set_scrolling_region(self.screen_size, 0, self.num_lines - 2)
|
||||||
|
|
||||||
@@ -178,7 +206,16 @@ class DiffHandler(Handler):
|
|||||||
def init_terminal_state(self):
|
def init_terminal_state(self):
|
||||||
self.cmd.set_line_wrapping(False)
|
self.cmd.set_line_wrapping(False)
|
||||||
self.cmd.set_window_title(main.title)
|
self.cmd.set_window_title(main.title)
|
||||||
self.cmd.set_default_colors(self.opts.foreground, self.opts.background)
|
self.cmd.set_default_colors(
|
||||||
|
fg=self.opts.foreground, bg=self.opts.background,
|
||||||
|
cursor=self.opts.foreground, select_fg=self.opts.select_fg,
|
||||||
|
select_bg=self.opts.select_bg)
|
||||||
|
self.cmd.set_cursor_shape('bar')
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
self.cmd.set_default_colors()
|
||||||
|
self.cmd.set_cursor_visible(True)
|
||||||
|
self.cmd.set_scrolling_region()
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self.init_terminal_state()
|
self.init_terminal_state()
|
||||||
@@ -187,12 +224,7 @@ class DiffHandler(Handler):
|
|||||||
self.create_collection()
|
self.create_collection()
|
||||||
|
|
||||||
def enforce_cursor_state(self):
|
def enforce_cursor_state(self):
|
||||||
self.cmd.set_cursor_visible(self.state > DIFFED)
|
self.cmd.set_cursor_visible(self.state == COMMAND)
|
||||||
|
|
||||||
def finalize(self):
|
|
||||||
self.cmd.set_cursor_visible(True)
|
|
||||||
self.cmd.set_default_colors()
|
|
||||||
self.cmd.set_scrolling_region()
|
|
||||||
|
|
||||||
def draw_lines(self, num, offset=0):
|
def draw_lines(self, num, offset=0):
|
||||||
offset += self.scroll_pos
|
offset += self.scroll_pos
|
||||||
@@ -208,6 +240,8 @@ class DiffHandler(Handler):
|
|||||||
if line.image_data is not None:
|
if line.image_data is not None:
|
||||||
image_involved = True
|
image_involved = True
|
||||||
self.write('\r\x1b[K' + text + '\x1b[0m')
|
self.write('\r\x1b[K' + text + '\x1b[0m')
|
||||||
|
if self.current_search is not None:
|
||||||
|
self.current_search.highlight_line(self.write, lpos)
|
||||||
if i < num - 1:
|
if i < num - 1:
|
||||||
self.write('\n')
|
self.write('\n')
|
||||||
if image_involved:
|
if image_involved:
|
||||||
@@ -262,19 +296,29 @@ class DiffHandler(Handler):
|
|||||||
def draw_status_line(self):
|
def draw_status_line(self):
|
||||||
if self.state < DIFFED:
|
if self.state < DIFFED:
|
||||||
return
|
return
|
||||||
|
self.enforce_cursor_state()
|
||||||
self.cmd.set_cursor_position(0, self.num_lines)
|
self.cmd.set_cursor_position(0, self.num_lines)
|
||||||
self.cmd.clear_to_eol()
|
self.cmd.clear_to_eol()
|
||||||
scroll_frac = styled('{:.0%}'.format(self.scroll_pos / (self.max_scroll_pos or 1)), fg=self.opts.margin_fg)
|
if self.state is COMMAND:
|
||||||
counts = '{}{}{}'.format(
|
self.line_edit.write(self.write)
|
||||||
styled(str(self.added_count), fg=self.opts.highlight_added_bg),
|
elif self.state is MESSAGE:
|
||||||
styled(',', fg=self.opts.margin_fg),
|
self.cmd.styled(self.message, reverse=True)
|
||||||
styled(str(self.removed_count), fg=self.opts.highlight_removed_bg)
|
else:
|
||||||
)
|
sp = '{:.0%}'.format(self.scroll_pos/self.max_scroll_pos) if self.scroll_pos and self.max_scroll_pos else '0%'
|
||||||
suffix = counts + ' ' + scroll_frac
|
scroll_frac = styled(sp, fg=self.opts.margin_fg)
|
||||||
prefix = styled(':', fg=self.opts.margin_fg)
|
if self.current_search is None:
|
||||||
filler = self.screen_size.cols - wcswidth(prefix) - wcswidth(suffix)
|
counts = '{}{}{}'.format(
|
||||||
text = '{}{}{}'.format(prefix, ' ' * filler, suffix)
|
styled(str(self.added_count), fg=self.opts.highlight_added_bg),
|
||||||
self.write(text)
|
styled(',', fg=self.opts.margin_fg),
|
||||||
|
styled(str(self.removed_count), fg=self.opts.highlight_removed_bg)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
counts = styled('{} matches'.format(len(self.current_search)), fg=self.opts.margin_fg)
|
||||||
|
suffix = counts + ' ' + scroll_frac
|
||||||
|
prefix = styled(':', fg=self.opts.margin_fg)
|
||||||
|
filler = self.screen_size.cols - wcswidth(prefix) - wcswidth(suffix)
|
||||||
|
text = '{}{}{}'.format(prefix, ' ' * filler, suffix)
|
||||||
|
self.write(text)
|
||||||
|
|
||||||
def change_context_count(self, new_ctx):
|
def change_context_count(self, new_ctx):
|
||||||
new_ctx = max(0, new_ctx)
|
new_ctx = max(0, new_ctx)
|
||||||
@@ -285,14 +329,75 @@ class DiffHandler(Handler):
|
|||||||
self.restore_position = self.current_position
|
self.restore_position = self.current_position
|
||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
|
|
||||||
|
def start_search(self, is_regex, is_backward):
|
||||||
|
if self.state != DIFFED:
|
||||||
|
self.cmd.bell()
|
||||||
|
return
|
||||||
|
self.state = COMMAND
|
||||||
|
self.line_edit.clear()
|
||||||
|
self.line_edit.add_text('?' if is_backward else '/')
|
||||||
|
self.current_search_is_regex = is_regex
|
||||||
|
self.draw_status_line()
|
||||||
|
|
||||||
|
def do_search(self):
|
||||||
|
self.current_search = None
|
||||||
|
query = self.line_edit.current_input
|
||||||
|
if len(query) < 2:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.current_search = Search(self.opts, query[1:], self.current_search_is_regex, query[0] == '?')
|
||||||
|
except BadRegex:
|
||||||
|
self.state = MESSAGE
|
||||||
|
self.message = sanitize(_('Bad regex: {}').format(query[1:]))
|
||||||
|
self.cmd.bell()
|
||||||
|
else:
|
||||||
|
if not self.current_search(self.diff_lines, self.margin_size, self.screen_size.cols):
|
||||||
|
self.state = MESSAGE
|
||||||
|
self.message = sanitize(_('No matches found'))
|
||||||
|
self.cmd.bell()
|
||||||
|
|
||||||
def on_text(self, text, in_bracketed_paste=False):
|
def on_text(self, text, in_bracketed_paste=False):
|
||||||
|
if self.state is COMMAND:
|
||||||
|
self.line_edit.on_text(text, in_bracketed_paste)
|
||||||
|
self.draw_status_line()
|
||||||
|
return
|
||||||
|
if self.state is MESSAGE:
|
||||||
|
self.state = DIFFED
|
||||||
|
self.draw_status_line()
|
||||||
|
return
|
||||||
action = self.shortcut_action(text)
|
action = self.shortcut_action(text)
|
||||||
if action is not None:
|
if action is not None:
|
||||||
return self.perform_action(action)
|
return self.perform_action(action)
|
||||||
|
|
||||||
def on_key(self, key_event):
|
def on_key(self, key_event):
|
||||||
|
if self.state is MESSAGE:
|
||||||
|
if key_event.type is not RELEASE:
|
||||||
|
self.state = DIFFED
|
||||||
|
self.draw_status_line()
|
||||||
|
return
|
||||||
|
if self.state is COMMAND:
|
||||||
|
if self.line_edit.on_key(key_event):
|
||||||
|
if not self.line_edit.current_input:
|
||||||
|
self.state = DIFFED
|
||||||
|
self.draw_status_line()
|
||||||
|
return
|
||||||
if key_event.type is RELEASE:
|
if key_event.type is RELEASE:
|
||||||
return
|
return
|
||||||
|
if self.state is COMMAND:
|
||||||
|
if key_event.key is ESCAPE:
|
||||||
|
self.state = DIFFED
|
||||||
|
self.draw_status_line()
|
||||||
|
return
|
||||||
|
if key_event is enter_key:
|
||||||
|
self.state = DIFFED
|
||||||
|
self.do_search()
|
||||||
|
self.line_edit.clear()
|
||||||
|
self.draw_screen()
|
||||||
|
return
|
||||||
|
if self.state >= DIFFED and self.current_search is not None and key_event.key is ESCAPE:
|
||||||
|
self.current_search = None
|
||||||
|
self.draw_screen()
|
||||||
|
return
|
||||||
action = self.shortcut_action(key_event)
|
action = self.shortcut_action(key_event)
|
||||||
if action is not None:
|
if action is not None:
|
||||||
return self.perform_action(action)
|
return self.perform_action(action)
|
||||||
@@ -399,11 +504,9 @@ def main(args):
|
|||||||
opts = init_config(args)
|
opts = init_config(args)
|
||||||
set_diff_command(opts.diff_cmd)
|
set_diff_command(opts.diff_cmd)
|
||||||
lines_for_path.replace_tab_by = opts.replace_tab_by
|
lines_for_path.replace_tab_by = opts.replace_tab_by
|
||||||
# Ensure the kitten works even when stdin/stdout are redirected
|
for f in left, right:
|
||||||
if not sys.stdin.isatty():
|
if not os.path.exists(f):
|
||||||
sys.stdin = open('/dev/tty')
|
raise SystemExit('{} does not exist'.format(f))
|
||||||
if not sys.stdout.isatty():
|
|
||||||
sys.stdout = open('/dev/tty', 'w')
|
|
||||||
|
|
||||||
loop = Loop()
|
loop = Loop()
|
||||||
handler = DiffHandler(args, opts, left, right)
|
handler = DiffHandler(args, opts, left, right)
|
||||||
|
|||||||
@@ -447,7 +447,7 @@ def render_diff(collection, diff_map, args, columns, image_manager):
|
|||||||
if patch is not None:
|
if patch is not None:
|
||||||
largest_line_number = max(largest_line_number, patch.largest_line_number)
|
largest_line_number = max(largest_line_number, patch.largest_line_number)
|
||||||
|
|
||||||
margin_size = max(3, len(str(largest_line_number)) + 1)
|
margin_size = render_diff.margin_size = max(3, len(str(largest_line_number)) + 1)
|
||||||
last_item_num = len(collection) - 1
|
last_item_num = len(collection) - 1
|
||||||
|
|
||||||
for i, (path, item_type, other_path) in enumerate(collection):
|
for i, (path, item_type, other_path) in enumerate(collection):
|
||||||
|
|||||||
67
kittens/diff/search.py
Normal file
67
kittens/diff/search.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from kitty.fast_data_types import wcswidth
|
||||||
|
|
||||||
|
from ..tui.operations import styled
|
||||||
|
|
||||||
|
|
||||||
|
class BadRegex(ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Search:
|
||||||
|
|
||||||
|
def __init__(self, opts, query, is_regex, is_backward):
|
||||||
|
self.matches = {}
|
||||||
|
self.count = 0
|
||||||
|
self.style = styled('|', fg=opts.search_fg, bg=opts.search_bg).split('|', 1)[0]
|
||||||
|
if not is_regex:
|
||||||
|
query = re.escape(query)
|
||||||
|
try:
|
||||||
|
self.pat = re.compile(query, flags=re.UNICODE | re.IGNORECASE)
|
||||||
|
except Exception:
|
||||||
|
raise BadRegex('Not a valid regex: {}'.format(query))
|
||||||
|
|
||||||
|
def __call__(self, diff_lines, margin_size, cols):
|
||||||
|
self.matches = {}
|
||||||
|
self.count = 0
|
||||||
|
half_width = cols // 2
|
||||||
|
strip_pat = re.compile('\033[[].*?m')
|
||||||
|
right_offset = half_width + 1 + margin_size
|
||||||
|
find = self.pat.finditer
|
||||||
|
for i, line in enumerate(diff_lines):
|
||||||
|
text = strip_pat.sub('', line.text)
|
||||||
|
left, right = text[margin_size:half_width + 1], text[right_offset:]
|
||||||
|
matches = []
|
||||||
|
|
||||||
|
def add(which, offset):
|
||||||
|
for m in find(which):
|
||||||
|
before = which[:m.start()]
|
||||||
|
matches.append((wcswidth(before) + offset, m.group()))
|
||||||
|
self.count += 1
|
||||||
|
|
||||||
|
add(left, margin_size)
|
||||||
|
add(right, right_offset)
|
||||||
|
if matches:
|
||||||
|
self.matches[i] = matches
|
||||||
|
return bool(self.matches)
|
||||||
|
|
||||||
|
def __contains__(self, i):
|
||||||
|
return i in self.matches
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.count
|
||||||
|
|
||||||
|
def highlight_line(self, write, line_num):
|
||||||
|
highlights = self.matches.get(line_num)
|
||||||
|
if not highlights:
|
||||||
|
return False
|
||||||
|
write(self.style)
|
||||||
|
for start, text in highlights:
|
||||||
|
write('\r\x1b[{}C{}'.format(start, text))
|
||||||
|
write('\x1b[m')
|
||||||
|
return True
|
||||||
@@ -1 +1 @@
|
|||||||
url_delimiters = '\x00-\x09\x0b-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa
|
url_delimiters = '\x00-\x09\x0b-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U000110cd\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa
|
||||||
128
kittens/tui/line_edit.py
Normal file
128
kittens/tui/line_edit.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
||||||
|
from kitty.key_encoding import RELEASE, HOME, END, BACKSPACE, DELETE, LEFT, RIGHT
|
||||||
|
|
||||||
|
|
||||||
|
class LineEdit:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.clear()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.current_input = ''
|
||||||
|
self.cursor_pos = 0
|
||||||
|
self.pending_bell = False
|
||||||
|
|
||||||
|
def split_at_cursor(self, delta=0):
|
||||||
|
pos = max(0, self.cursor_pos + delta)
|
||||||
|
x = truncate_point_for_length(self.current_input, pos) if pos else 0
|
||||||
|
before, after = self.current_input[:x], self.current_input[x:]
|
||||||
|
return before, after
|
||||||
|
|
||||||
|
def write(self, write, prompt=''):
|
||||||
|
if self.pending_bell:
|
||||||
|
write('\a')
|
||||||
|
self.pending_bell = False
|
||||||
|
write(prompt)
|
||||||
|
write(self.current_input)
|
||||||
|
write('\r\x1b[{}C'.format(self.cursor_pos + wcswidth(prompt)))
|
||||||
|
|
||||||
|
def add_text(self, text):
|
||||||
|
if self.current_input:
|
||||||
|
x = truncate_point_for_length(self.current_input, self.cursor_pos) if self.cursor_pos else 0
|
||||||
|
self.current_input = self.current_input[:x] + text + self.current_input[x:]
|
||||||
|
else:
|
||||||
|
self.current_input = text
|
||||||
|
self.cursor_pos += wcswidth(text)
|
||||||
|
|
||||||
|
def on_text(self, text, in_bracketed_paste):
|
||||||
|
self.add_text(text)
|
||||||
|
|
||||||
|
def backspace(self, num=1):
|
||||||
|
before, after = self.split_at_cursor()
|
||||||
|
nbefore = before[:-num]
|
||||||
|
if nbefore != before:
|
||||||
|
self.current_input = nbefore + after
|
||||||
|
self.cursor_pos = wcswidth(nbefore)
|
||||||
|
return True
|
||||||
|
self.pending_bell = True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete(self, num=1):
|
||||||
|
before, after = self.split_at_cursor()
|
||||||
|
nafter = after[num:]
|
||||||
|
if nafter != after:
|
||||||
|
self.current_input = before + nafter
|
||||||
|
self.cursor_pos = wcswidth(before)
|
||||||
|
return True
|
||||||
|
self.pending_bell = True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _left(self):
|
||||||
|
if not self.current_input:
|
||||||
|
self.cursor_pos = 0
|
||||||
|
return
|
||||||
|
if self.cursor_pos:
|
||||||
|
before, after = self.split_at_cursor(-1)
|
||||||
|
self.cursor_pos = wcswidth(before)
|
||||||
|
|
||||||
|
def _right(self):
|
||||||
|
if not self.current_input:
|
||||||
|
self.cursor_pos = 0
|
||||||
|
return
|
||||||
|
max_pos = wcswidth(self.current_input)
|
||||||
|
if self.cursor_pos >= max_pos:
|
||||||
|
self.cursor_pos = max_pos
|
||||||
|
return
|
||||||
|
before, after = self.split_at_cursor(1)
|
||||||
|
self.cursor_pos += 1 + int(wcswidth(before) == self.cursor_pos)
|
||||||
|
|
||||||
|
def _move_loop(self, func, num):
|
||||||
|
before = self.cursor_pos
|
||||||
|
while func() and num > 0:
|
||||||
|
num -= 1
|
||||||
|
changed = self.cursor_pos != before
|
||||||
|
if not changed:
|
||||||
|
self.pending_bell = True
|
||||||
|
return changed
|
||||||
|
|
||||||
|
def left(self, num=1):
|
||||||
|
return self._move_loop(self._left, num)
|
||||||
|
|
||||||
|
def right(self, num=1):
|
||||||
|
return self._move_loop(self._right, num)
|
||||||
|
|
||||||
|
def home(self):
|
||||||
|
if self.cursor_pos:
|
||||||
|
self.cursor_pos = 0
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def end(self):
|
||||||
|
orig = self.cursor_pos
|
||||||
|
self.cursor_pos = wcswidth(self.current_input) + 1
|
||||||
|
return self.cursor_pos != orig
|
||||||
|
|
||||||
|
def on_key(self, key_event):
|
||||||
|
if key_event.type is RELEASE:
|
||||||
|
return False
|
||||||
|
elif key_event.key is HOME:
|
||||||
|
return self.home()
|
||||||
|
elif key_event.key is END:
|
||||||
|
return self.end()
|
||||||
|
elif key_event.key is BACKSPACE:
|
||||||
|
self.backspace()
|
||||||
|
return True
|
||||||
|
elif key_event.key is DELETE:
|
||||||
|
self.delete()
|
||||||
|
return True
|
||||||
|
elif key_event.key is LEFT:
|
||||||
|
self.left()
|
||||||
|
return True
|
||||||
|
elif key_event.key is RIGHT:
|
||||||
|
self.right()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
@@ -235,7 +235,7 @@ class Loop:
|
|||||||
self.handler.on_eot()
|
self.handler.on_eot()
|
||||||
else:
|
else:
|
||||||
self.handler.on_text(chunk, self.in_bracketed_paste)
|
self.handler.on_text(chunk, self.in_bracketed_paste)
|
||||||
else:
|
elif chunk:
|
||||||
self.handler.on_text(chunk, self.in_bracketed_paste)
|
self.handler.on_text(chunk, self.in_bracketed_paste)
|
||||||
|
|
||||||
def _on_dcs(self, dcs):
|
def _on_dcs(self, dcs):
|
||||||
|
|||||||
@@ -78,6 +78,13 @@ def set_cursor_position(x, y) -> str: # (0, 0) is top left
|
|||||||
return '\033[{};{}H'.format(y + 1, x + 1)
|
return '\033[{};{}H'.format(y + 1, x + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def set_cursor_shape(shape='block', blink=True) -> str:
|
||||||
|
val = {'block': 1, 'underline': 3, 'bar': 5}.get(shape, 1)
|
||||||
|
if not blink:
|
||||||
|
val += 1
|
||||||
|
return '\033[{} q'.format(val)
|
||||||
|
|
||||||
|
|
||||||
def set_scrolling_region(screen_size=None, top=None, bottom=None) -> str:
|
def set_scrolling_region(screen_size=None, top=None, bottom=None) -> str:
|
||||||
if screen_size is None:
|
if screen_size is None:
|
||||||
return '\033[r'
|
return '\033[r'
|
||||||
@@ -182,10 +189,11 @@ def init_state(alternate_screen=True):
|
|||||||
reset_mode('IRM') + reset_mode('DECKM') + reset_mode('DECSCNM') +
|
reset_mode('IRM') + reset_mode('DECKM') + reset_mode('DECSCNM') +
|
||||||
set_mode('DECARM') + reset_mode('DECOM') + set_mode('DECAWM') +
|
set_mode('DECARM') + reset_mode('DECOM') + set_mode('DECAWM') +
|
||||||
set_mode('DECTCEM') + reset_mode('MOUSE_BUTTON_TRACKING') +
|
set_mode('DECTCEM') + reset_mode('MOUSE_BUTTON_TRACKING') +
|
||||||
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING')
|
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING') +
|
||||||
+ reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') +
|
reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') +
|
||||||
reset_mode('MOUSE_SGR_MODE') + reset_mode('MOUSE_UTF8_MODE') +
|
reset_mode('MOUSE_SGR_MODE') + reset_mode('MOUSE_UTF8_MODE') +
|
||||||
set_mode('BRACKETED_PASTE') + set_mode('EXTENDED_KEYBOARD') +
|
set_mode('BRACKETED_PASTE') + set_mode('EXTENDED_KEYBOARD') +
|
||||||
|
'\033]30001\033\\' +
|
||||||
'\033[*x' # reset DECSACE to default region select
|
'\033[*x' # reset DECSACE to default region select
|
||||||
)
|
)
|
||||||
if alternate_screen:
|
if alternate_screen:
|
||||||
@@ -200,6 +208,7 @@ def reset_state(normal_screen=True):
|
|||||||
ans += reset_mode('ALTERNATE_SCREEN')
|
ans += reset_mode('ALTERNATE_SCREEN')
|
||||||
ans += RESTORE_PRIVATE_MODE_VALUES
|
ans += RESTORE_PRIVATE_MODE_VALUES
|
||||||
ans += RESTORE_CURSOR
|
ans += RESTORE_CURSOR
|
||||||
|
ans += '\033]30101\033\\'
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
@@ -218,16 +227,21 @@ def alternate_screen(f=None):
|
|||||||
print(reset_mode('ALTERNATE_SCREEN'), end='', file=f)
|
print(reset_mode('ALTERNATE_SCREEN'), end='', file=f)
|
||||||
|
|
||||||
|
|
||||||
def set_default_colors(fg=None, bg=None) -> str:
|
def set_default_colors(fg=None, bg=None, cursor=None, select_bg=None, select_fg=None) -> str:
|
||||||
ans = ''
|
ans = ''
|
||||||
if fg is None:
|
|
||||||
ans += '\x1b]110\x1b\\'
|
def item(which, num):
|
||||||
else:
|
nonlocal ans
|
||||||
ans += '\x1b]10;{}\x1b\\'.format(color_as_sharp(fg if isinstance(fg, Color) else to_color(fg)))
|
if which is None:
|
||||||
if bg is None:
|
ans += '\x1b]1{}\x1b\\'.format(num)
|
||||||
ans += '\x1b]111\x1b\\'
|
else:
|
||||||
else:
|
ans += '\x1b]{};{}\x1b\\'.format(num, color_as_sharp(which if isinstance(which, Color) else to_color(which)))
|
||||||
ans += '\x1b]11;{}\x1b\\'.format(color_as_sharp(bg if isinstance(bg, Color) else to_color(bg)))
|
|
||||||
|
item(fg, 10)
|
||||||
|
item(bg, 11)
|
||||||
|
item(cursor, 12)
|
||||||
|
item(select_bg, 17)
|
||||||
|
item(select_fg, 19)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ from kitty.utils import get_editor
|
|||||||
from kitty.fast_data_types import wcswidth
|
from kitty.fast_data_types import wcswidth
|
||||||
from kitty.key_encoding import (
|
from kitty.key_encoding import (
|
||||||
DOWN, ESCAPE, F1, F2, F3, F4, F12, LEFT, RELEASE, RIGHT, SHIFT, TAB, UP,
|
DOWN, ESCAPE, F1, F2, F3, F4, F12, LEFT, RELEASE, RIGHT, SHIFT, TAB, UP,
|
||||||
backspace_key, enter_key
|
enter_key
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ..tui.line_edit import LineEdit
|
||||||
from ..tui.handler import Handler
|
from ..tui.handler import Handler
|
||||||
from ..tui.loop import Loop
|
from ..tui.loop import Loop
|
||||||
from ..tui.operations import (
|
from ..tui.operations import (
|
||||||
@@ -247,8 +248,8 @@ class UnicodeInput(Handler):
|
|||||||
|
|
||||||
def __init__(self, cached_values):
|
def __init__(self, cached_values):
|
||||||
self.cached_values = cached_values
|
self.cached_values = cached_values
|
||||||
|
self.line_edit = LineEdit()
|
||||||
self.recent = list(self.cached_values.get('recent', DEFAULT_SET))
|
self.recent = list(self.cached_values.get('recent', DEFAULT_SET))
|
||||||
self.current_input = ''
|
|
||||||
self.current_char = None
|
self.current_char = None
|
||||||
self.prompt_template = '{}> '
|
self.prompt_template = '{}> '
|
||||||
self.last_updated_code_point_at = None
|
self.last_updated_code_point_at = None
|
||||||
@@ -269,9 +270,9 @@ class UnicodeInput(Handler):
|
|||||||
codepoints = load_favorites()
|
codepoints = load_favorites()
|
||||||
q = self.mode, tuple(codepoints)
|
q = self.mode, tuple(codepoints)
|
||||||
elif self.mode is NAME:
|
elif self.mode is NAME:
|
||||||
q = self.mode, self.current_input
|
q = self.mode, self.line_edit.current_input
|
||||||
if q != self.last_updated_code_point_at:
|
if q != self.last_updated_code_point_at:
|
||||||
words = self.current_input.split()
|
words = self.line_edit.current_input.split()
|
||||||
words = [w for w in words if w != INDEX_CHAR]
|
words = [w for w in words if w != INDEX_CHAR]
|
||||||
index_words = [i for i, w in enumerate(words) if i > 0 and is_index(w)]
|
index_words = [i for i, w in enumerate(words) if i > 0 and is_index(w)]
|
||||||
if index_words:
|
if index_words:
|
||||||
@@ -291,10 +292,10 @@ class UnicodeInput(Handler):
|
|||||||
self.current_char = None
|
self.current_char = None
|
||||||
if self.mode is HEX:
|
if self.mode is HEX:
|
||||||
try:
|
try:
|
||||||
if self.current_input.startswith(INDEX_CHAR) and len(self.current_input) > 1:
|
if self.line_edit.current_input.startswith(INDEX_CHAR) and len(self.line_edit.current_input) > 1:
|
||||||
self.current_char = chr(self.table.codepoint_at_hint(self.current_input[1:]))
|
self.current_char = chr(self.table.codepoint_at_hint(self.line_edit.current_input[1:]))
|
||||||
else:
|
else:
|
||||||
code = int(self.current_input, 16)
|
code = int(self.line_edit.current_input, 16)
|
||||||
self.current_char = chr(code)
|
self.current_char = chr(code)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -304,8 +305,8 @@ class UnicodeInput(Handler):
|
|||||||
self.current_char = chr(cc)
|
self.current_char = chr(cc)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if self.current_input:
|
if self.line_edit.current_input:
|
||||||
self.current_char = chr(self.table.codepoint_at_hint(self.current_input.lstrip(INDEX_CHAR)))
|
self.current_char = chr(self.table.codepoint_at_hint(self.line_edit.current_input.lstrip(INDEX_CHAR)))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
if self.current_char is not None:
|
if self.current_char is not None:
|
||||||
@@ -366,8 +367,7 @@ class UnicodeInput(Handler):
|
|||||||
writeln(_('Enter the hex code for the character'))
|
writeln(_('Enter the hex code for the character'))
|
||||||
else:
|
else:
|
||||||
writeln(_('Enter the index for the character you want from the list below'))
|
writeln(_('Enter the index for the character you want from the list below'))
|
||||||
self.write(self.prompt)
|
self.line_edit.write(self.write, self.prompt)
|
||||||
self.write(self.current_input)
|
|
||||||
with cursor(self.write):
|
with cursor(self.write):
|
||||||
writeln()
|
writeln()
|
||||||
writeln(self.choice_line)
|
writeln(self.choice_line)
|
||||||
@@ -385,14 +385,34 @@ class UnicodeInput(Handler):
|
|||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
|
|
||||||
def on_text(self, text, in_bracketed_paste):
|
def on_text(self, text, in_bracketed_paste):
|
||||||
self.current_input += text
|
self.line_edit.on_text(text, in_bracketed_paste)
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
def on_key(self, key_event):
|
def on_key(self, key_event):
|
||||||
if key_event is backspace_key:
|
if self.mode is NAME and key_event.type is not RELEASE and not key_event.mods:
|
||||||
self.current_input = self.current_input[:-1]
|
if key_event.key is TAB:
|
||||||
|
if key_event.mods == SHIFT:
|
||||||
|
self.table.move_current(cols=-1), self.refresh()
|
||||||
|
elif not key_event.mods:
|
||||||
|
self.table.move_current(cols=1), self.refresh()
|
||||||
|
return
|
||||||
|
elif key_event.key is LEFT and not key_event.mods:
|
||||||
|
self.table.move_current(cols=-1), self.refresh()
|
||||||
|
return
|
||||||
|
elif key_event.key is RIGHT and not key_event.mods:
|
||||||
|
self.table.move_current(cols=1), self.refresh()
|
||||||
|
return
|
||||||
|
elif key_event.key is UP and not key_event.mods:
|
||||||
|
self.table.move_current(rows=-1), self.refresh()
|
||||||
|
return
|
||||||
|
elif key_event.key is DOWN and not key_event.mods:
|
||||||
|
self.table.move_current(rows=1), self.refresh()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.line_edit.on_key(key_event):
|
||||||
self.refresh()
|
self.refresh()
|
||||||
elif key_event is enter_key:
|
return
|
||||||
|
if key_event is enter_key:
|
||||||
self.quit_loop(0)
|
self.quit_loop(0)
|
||||||
elif key_event.type is RELEASE and not key_event.mods:
|
elif key_event.type is RELEASE and not key_event.mods:
|
||||||
if key_event.key is ESCAPE:
|
if key_event.key is ESCAPE:
|
||||||
@@ -407,20 +427,6 @@ class UnicodeInput(Handler):
|
|||||||
self.switch_mode(FAVORITES)
|
self.switch_mode(FAVORITES)
|
||||||
elif key_event.key is F12 and self.mode is FAVORITES:
|
elif key_event.key is F12 and self.mode is FAVORITES:
|
||||||
self.edit_favorites()
|
self.edit_favorites()
|
||||||
elif self.mode is NAME:
|
|
||||||
if key_event.key is TAB:
|
|
||||||
if key_event.mods == SHIFT:
|
|
||||||
self.table.move_current(cols=-1), self.refresh()
|
|
||||||
elif not key_event.mods:
|
|
||||||
self.table.move_current(cols=1), self.refresh()
|
|
||||||
elif key_event.key is LEFT and not key_event.mods:
|
|
||||||
self.table.move_current(cols=-1), self.refresh()
|
|
||||||
elif key_event.key is RIGHT and not key_event.mods:
|
|
||||||
self.table.move_current(cols=1), self.refresh()
|
|
||||||
elif key_event.key is UP and not key_event.mods:
|
|
||||||
self.table.move_current(rows=-1), self.refresh()
|
|
||||||
elif key_event.key is DOWN and not key_event.mods:
|
|
||||||
self.table.move_current(rows=1), self.refresh()
|
|
||||||
|
|
||||||
def edit_favorites(self):
|
def edit_favorites(self):
|
||||||
if not os.path.exists(favorites_path):
|
if not os.path.exists(favorites_path):
|
||||||
@@ -437,7 +443,7 @@ class UnicodeInput(Handler):
|
|||||||
if mode is not self.mode:
|
if mode is not self.mode:
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.cached_values['mode'] = mode
|
self.cached_values['mode'] = mode
|
||||||
self.current_input = ''
|
self.line_edit.clear()
|
||||||
self.current_char = None
|
self.current_char = None
|
||||||
self.choice_line = ''
|
self.choice_line = ''
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|||||||
55135
kittens/unicode_input/names.h
generated
55135
kittens/unicode_input/names.h
generated
File diff suppressed because one or more lines are too long
@@ -81,6 +81,7 @@ class Boss:
|
|||||||
self.pending_sequences = None
|
self.pending_sequences = None
|
||||||
self.cached_values = cached_values
|
self.cached_values = cached_values
|
||||||
self.os_window_map = {}
|
self.os_window_map = {}
|
||||||
|
self.os_window_death_actions = {}
|
||||||
self.cursor_blinking = True
|
self.cursor_blinking = True
|
||||||
self.shutting_down = False
|
self.shutting_down = False
|
||||||
talk_fd = getattr(single_instance, 'socket', None)
|
talk_fd = getattr(single_instance, 'socket', None)
|
||||||
@@ -95,7 +96,7 @@ class Boss:
|
|||||||
)
|
)
|
||||||
set_boss(self)
|
set_boss(self)
|
||||||
self.opts, self.args = opts, args
|
self.opts, self.args = opts, args
|
||||||
startup_session = create_session(opts, args)
|
startup_session = create_session(opts, args, default_session=opts.startup_session)
|
||||||
self.keymap = self.opts.keymap.copy()
|
self.keymap = self.opts.keymap.copy()
|
||||||
if new_os_window_trigger is not None:
|
if new_os_window_trigger is not None:
|
||||||
self.keymap.pop(new_os_window_trigger, None)
|
self.keymap.pop(new_os_window_trigger, None)
|
||||||
@@ -239,7 +240,9 @@ class Boss:
|
|||||||
if not os.path.isabs(args.directory):
|
if not os.path.isabs(args.directory):
|
||||||
args.directory = os.path.join(msg['cwd'], args.directory)
|
args.directory = os.path.join(msg['cwd'], args.directory)
|
||||||
session = create_session(opts, args, respect_cwd=True)
|
session = create_session(opts, args, respect_cwd=True)
|
||||||
self.add_os_window(session, wclass=args.cls, wname=args.name, opts_for_size=opts, startup_id=startup_id)
|
os_window_id = self.add_os_window(session, wclass=args.cls, wname=args.name, opts_for_size=opts, startup_id=startup_id)
|
||||||
|
if msg.get('notify_on_os_window_death'):
|
||||||
|
self.os_window_death_actions[os_window_id] = partial(self.notify_on_os_window_death, msg['notify_on_os_window_death'])
|
||||||
else:
|
else:
|
||||||
log_error('Unknown message received from peer, ignoring')
|
log_error('Unknown message received from peer, ignoring')
|
||||||
|
|
||||||
@@ -311,10 +314,12 @@ class Boss:
|
|||||||
tm.resize()
|
tm.resize()
|
||||||
|
|
||||||
def increase_font_size(self): # legacy
|
def increase_font_size(self): # legacy
|
||||||
self.set_font_size(min(self.opts.font_size * 5, self.current_font_size + 2.0))
|
cfs = global_font_size()
|
||||||
|
self.set_font_size(min(self.opts.font_size * 5, cfs + 2.0))
|
||||||
|
|
||||||
def decrease_font_size(self): # legacy
|
def decrease_font_size(self): # legacy
|
||||||
self.set_font_size(self.current_font_size - self.opts.font_size_delta)
|
cfs = global_font_size()
|
||||||
|
self.set_font_size(max(MINIMUM_FONT_SIZE, cfs - 2.0))
|
||||||
|
|
||||||
def restore_font_size(self): # legacy
|
def restore_font_size(self): # legacy
|
||||||
self.set_font_size(self.opts.font_size)
|
self.set_font_size(self.opts.font_size)
|
||||||
@@ -527,6 +532,23 @@ class Boss:
|
|||||||
tm.destroy()
|
tm.destroy()
|
||||||
for window_id in tuple(w.id for w in self.window_id_map.values() if getattr(w, 'os_window_id', None) == os_window_id):
|
for window_id in tuple(w.id for w in self.window_id_map.values() if getattr(w, 'os_window_id', None) == os_window_id):
|
||||||
self.window_id_map.pop(window_id, None)
|
self.window_id_map.pop(window_id, None)
|
||||||
|
action = self.os_window_death_actions.pop(os_window_id, None)
|
||||||
|
if action is not None:
|
||||||
|
action()
|
||||||
|
|
||||||
|
def notify_on_os_window_death(self, address):
|
||||||
|
import socket
|
||||||
|
s = socket.socket(family=socket.AF_UNIX)
|
||||||
|
try:
|
||||||
|
s.connect(address)
|
||||||
|
s.sendall(b'c')
|
||||||
|
try:
|
||||||
|
s.shutdown(socket.SHUT_RDWR)
|
||||||
|
except EnvironmentError:
|
||||||
|
pass
|
||||||
|
s.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def display_scrollback(self, window, data, cmd):
|
def display_scrollback(self, window, data, cmd):
|
||||||
tab = self.active_tab
|
tab = self.active_tab
|
||||||
|
|||||||
@@ -91,6 +91,14 @@ with the same :option:`kitty --instance-group` will result in new windows being
|
|||||||
in the first :italic:`{appname}` instance within that group
|
in the first :italic:`{appname}` instance within that group
|
||||||
|
|
||||||
|
|
||||||
|
--wait-for-single-instance-window-close
|
||||||
|
type=bool-set
|
||||||
|
Normally, when using :option:`--single-instance`, :italic:`{appname}` will open a new window in an existing
|
||||||
|
instance and quit immediately. With this option, it will not quit till the newly opened
|
||||||
|
window is closed. Note that if no previous instance is found, then :italic:`{appname}` will wait anyway,
|
||||||
|
regardless of this option.
|
||||||
|
|
||||||
|
|
||||||
--listen-on
|
--listen-on
|
||||||
Tell kitty to listen on the specified address for control
|
Tell kitty to listen on the specified address for control
|
||||||
messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or
|
messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or
|
||||||
|
|||||||
@@ -108,8 +108,8 @@ patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
|
|||||||
v = PyDict_GetItemString(spec, key);
|
v = PyDict_GetItemString(spec, key);
|
||||||
if (v) {
|
if (v) {
|
||||||
color_type color = PyLong_AsUnsignedLong(v);
|
color_type color = PyLong_AsUnsignedLong(v);
|
||||||
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) {
|
for (Py_ssize_t j = 0; j < PyTuple_GET_SIZE(profiles); j++) {
|
||||||
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i);
|
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, j);
|
||||||
self->color_table[i] = color;
|
self->color_table[i] = color;
|
||||||
if (change_configured) self->orig_color_table[i] = color;
|
if (change_configured) self->orig_color_table[i] = color;
|
||||||
self->dirty = true;
|
self->dirty = true;
|
||||||
@@ -220,6 +220,21 @@ copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size
|
|||||||
self->dirty = false;
|
self->dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
colorprofile_push_dynamic_colors(ColorProfile *self) {
|
||||||
|
if (self->dynamic_color_stack_idx >= arraysz(self->dynamic_color_stack)) {
|
||||||
|
memmove(self->dynamic_color_stack, self->dynamic_color_stack + 1, sizeof(self->dynamic_color_stack) - sizeof(self->dynamic_color_stack[0]));
|
||||||
|
self->dynamic_color_stack_idx = arraysz(self->dynamic_color_stack) - 1;
|
||||||
|
}
|
||||||
|
self->dynamic_color_stack[self->dynamic_color_stack_idx++] = self->overridden;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
colorprofile_pop_dynamic_colors(ColorProfile *self) {
|
||||||
|
if (!self->dynamic_color_stack_idx) return;
|
||||||
|
self->overridden = self->dynamic_color_stack[--(self->dynamic_color_stack_idx)];
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
color_table_address(ColorProfile *self, PyObject *a UNUSED) {
|
color_table_address(ColorProfile *self, PyObject *a UNUSED) {
|
||||||
#define color_table_address_doc "Pointer address to start of color table"
|
#define color_table_address_doc "Pointer address to start of color table"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
# Utils {{{
|
# Utils {{{
|
||||||
|
import os
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
|
||||||
from . import fast_data_types as defines
|
from . import fast_data_types as defines
|
||||||
@@ -10,7 +11,7 @@ from .conf.definition import option_func
|
|||||||
from .conf.utils import (
|
from .conf.utils import (
|
||||||
choices, positive_float, positive_int, to_cmdline, to_color, unit_float
|
choices, positive_float, positive_int, to_cmdline, to_color, unit_float
|
||||||
)
|
)
|
||||||
from .constants import is_macos
|
from .constants import config_dir, is_macos
|
||||||
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
|
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
|
||||||
from .layout import all_layouts
|
from .layout import all_layouts
|
||||||
from .rgb import color_as_int, color_as_sharp, color_from_int
|
from .rgb import color_as_int, color_as_sharp, color_from_int
|
||||||
@@ -616,6 +617,25 @@ opening new windows, closing windows, reading the content of windows, etc.
|
|||||||
Note that this even works over ssh connections.
|
Note that this even works over ssh connections.
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
|
||||||
|
def startup_session(x):
|
||||||
|
if x.lower() == 'none':
|
||||||
|
return
|
||||||
|
x = os.path.expanduser(x)
|
||||||
|
x = os.path.expandvars(x)
|
||||||
|
if not os.path.isabs(x):
|
||||||
|
x = os.path.join(config_dir, x)
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
o('startup_session', 'none', option_type=startup_session, long_text=_('''
|
||||||
|
Path to a session file to use for all kitty instances. Can be overridden
|
||||||
|
by using the :option:`kitty --session` command line option for individual
|
||||||
|
instances. See :ref:`sessions` in the kitty documentation for details. Note
|
||||||
|
that relative paths are interpreted with respect to the kitty config directory.
|
||||||
|
Environment variables in the path are expanded.
|
||||||
|
'''))
|
||||||
|
|
||||||
o('clipboard_control', 'write-clipboard write-primary', option_type=lambda x: frozenset(x.lower().split()), long_text=_('''
|
o('clipboard_control', 'write-clipboard write-primary', option_type=lambda x: frozenset(x.lower().split()), long_text=_('''
|
||||||
Allow programs running in kitty to read and write from the clipboard. You can
|
Allow programs running in kitty to read and write from the clipboard. You can
|
||||||
control exactly which actions are allowed. The set of possible actions is:
|
control exactly which actions are allowed. The set of possible actions is:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from collections import namedtuple
|
|||||||
from .fast_data_types import set_boss as set_c_boss
|
from .fast_data_types import set_boss as set_c_boss
|
||||||
|
|
||||||
appname = 'kitty'
|
appname = 'kitty'
|
||||||
version = (0, 11, 0)
|
version = (0, 11, 1)
|
||||||
str_version = '.'.join(map(str, version))
|
str_version = '.'.join(map(str, version))
|
||||||
_plat = sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
is_macos = 'darwin' in _plat
|
is_macos = 'darwin' in _plat
|
||||||
|
|||||||
@@ -211,6 +211,8 @@ typedef struct {
|
|||||||
bool dirty;
|
bool dirty;
|
||||||
uint32_t color_table[256];
|
uint32_t color_table[256];
|
||||||
uint32_t orig_color_table[256];
|
uint32_t orig_color_table[256];
|
||||||
|
DynamicColor dynamic_color_stack[10];
|
||||||
|
size_t dynamic_color_stack_idx;
|
||||||
DynamicColor configured, overridden;
|
DynamicColor configured, overridden;
|
||||||
} ColorProfile;
|
} ColorProfile;
|
||||||
|
|
||||||
@@ -275,6 +277,8 @@ bool set_iutf8(int, bool);
|
|||||||
|
|
||||||
color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval);
|
color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval);
|
||||||
void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride);
|
void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride);
|
||||||
|
void colorprofile_push_dynamic_colors(ColorProfile*);
|
||||||
|
void colorprofile_pop_dynamic_colors(ColorProfile*);
|
||||||
|
|
||||||
void set_mouse_cursor(MouseShape);
|
void set_mouse_cursor(MouseShape);
|
||||||
void mouse_event(int, int);
|
void mouse_event(int, int);
|
||||||
|
|||||||
2
kitty/emoji.h
generated
2
kitty/emoji.h
generated
@@ -1,4 +1,4 @@
|
|||||||
// unicode data, built from the unicode standard on: 2018-05-01
|
// unicode data, built from the unicode standard on: 2018-06-14
|
||||||
// see gen-wcwidth.py
|
// see gen-wcwidth.py
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
|
|||||||
@@ -15,17 +15,55 @@ from .constants import (
|
|||||||
appname, config_dir, glfw_path, is_macos, is_wayland, logo_data_file
|
appname, config_dir, glfw_path, is_macos, is_wayland, logo_data_file
|
||||||
)
|
)
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
create_os_window, free_font_data, glfw_init, glfw_terminate,
|
GLFW_MOD_SUPER, create_os_window, free_font_data, glfw_init,
|
||||||
set_default_window_icon, set_options, GLFW_MOD_SUPER
|
glfw_terminate, set_default_window_icon, set_options
|
||||||
)
|
)
|
||||||
from .fonts.box_drawing import set_scale
|
from .fonts.box_drawing import set_scale
|
||||||
from .fonts.render import set_font_family
|
from .fonts.render import set_font_family
|
||||||
from .utils import (
|
from .utils import (
|
||||||
detach, log_error, single_instance, startup_notification_handler
|
detach, log_error, single_instance, startup_notification_handler,
|
||||||
|
unix_socket_paths
|
||||||
)
|
)
|
||||||
from .window import load_shader_programs
|
from .window import load_shader_programs
|
||||||
|
|
||||||
|
|
||||||
|
def talk_to_instance(args):
|
||||||
|
import json
|
||||||
|
import socket
|
||||||
|
data = {'cmd': 'new_instance', 'args': tuple(sys.argv),
|
||||||
|
'startup_id': os.environ.get('DESKTOP_STARTUP_ID'),
|
||||||
|
'cwd': os.getcwd()}
|
||||||
|
notify_socket = None
|
||||||
|
if args.wait_for_single_instance_window_close:
|
||||||
|
address = '\0{}-os-window-close-notify-{}-{}'.format(appname, os.getpid(), os.geteuid())
|
||||||
|
notify_socket = socket.socket(family=socket.AF_UNIX)
|
||||||
|
try:
|
||||||
|
notify_socket.bind(address)
|
||||||
|
except FileNotFoundError:
|
||||||
|
for address in unix_socket_paths(address[1:], ext='.sock'):
|
||||||
|
notify_socket.bind(address)
|
||||||
|
break
|
||||||
|
data['notify_on_os_window_death'] = address
|
||||||
|
notify_socket.listen()
|
||||||
|
|
||||||
|
data = json.dumps(data, ensure_ascii=False).encode('utf-8')
|
||||||
|
single_instance.socket.sendall(data)
|
||||||
|
try:
|
||||||
|
single_instance.socket.shutdown(socket.SHUT_RDWR)
|
||||||
|
except EnvironmentError:
|
||||||
|
pass
|
||||||
|
single_instance.socket.close()
|
||||||
|
|
||||||
|
if args.wait_for_single_instance_window_close:
|
||||||
|
conn = notify_socket.accept()[0]
|
||||||
|
conn.recv(1)
|
||||||
|
try:
|
||||||
|
conn.shutdown(socket.SHUT_RDWR)
|
||||||
|
except EnvironmentError:
|
||||||
|
pass
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
def load_all_shaders(semi_transparent=0):
|
def load_all_shaders(semi_transparent=0):
|
||||||
load_shader_programs(semi_transparent)
|
load_shader_programs(semi_transparent)
|
||||||
load_borders_program()
|
load_borders_program()
|
||||||
@@ -200,12 +238,7 @@ def _main():
|
|||||||
if args.single_instance:
|
if args.single_instance:
|
||||||
is_first = single_instance(args.instance_group)
|
is_first = single_instance(args.instance_group)
|
||||||
if not is_first:
|
if not is_first:
|
||||||
import json
|
talk_to_instance(args)
|
||||||
data = {'cmd': 'new_instance', 'args': tuple(sys.argv),
|
|
||||||
'startup_id': os.environ.get('DESKTOP_STARTUP_ID'),
|
|
||||||
'cwd': os.getcwd()}
|
|
||||||
data = json.dumps(data, ensure_ascii=False).encode('utf-8')
|
|
||||||
single_instance.socket.sendall(data)
|
|
||||||
return
|
return
|
||||||
opts = create_opts(args)
|
opts = create_opts(args)
|
||||||
if opts.editor != '.':
|
if opts.editor != '.':
|
||||||
|
|||||||
@@ -344,6 +344,14 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
|||||||
case 52:
|
case 52:
|
||||||
DISPATCH_OSC(clipboard_control);
|
DISPATCH_OSC(clipboard_control);
|
||||||
break;
|
break;
|
||||||
|
case 30001:
|
||||||
|
REPORT_COMMAND(screen_push_dynamic_colors);
|
||||||
|
screen_push_dynamic_colors(screen);
|
||||||
|
break;
|
||||||
|
case 30101:
|
||||||
|
REPORT_COMMAND(screen_pop_dynamic_colors);
|
||||||
|
screen_pop_dynamic_colors(screen);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
REPORT_ERROR("Unknown OSC code: %u", code);
|
REPORT_ERROR("Unknown OSC code: %u", code);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ void
|
|||||||
screen_reset(Screen *self) {
|
screen_reset(Screen *self) {
|
||||||
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self);
|
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self);
|
||||||
linebuf_clear(self->linebuf, BLANK_CHAR);
|
linebuf_clear(self->linebuf, BLANK_CHAR);
|
||||||
|
historybuf_clear(self->historybuf);
|
||||||
grman_clear(self->grman, false, self->cell_size);
|
grman_clear(self->grman, false, self->cell_size);
|
||||||
self->modes = empty_modes;
|
self->modes = empty_modes;
|
||||||
#define R(name) self->color_profile->overridden.name = 0
|
#define R(name) self->color_profile->overridden.name = 0
|
||||||
@@ -1263,6 +1264,16 @@ screen_handle_cmd(Screen *self, PyObject *cmd) {
|
|||||||
CALLBACK("handle_remote_cmd", "O", cmd);
|
CALLBACK("handle_remote_cmd", "O", cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
screen_push_dynamic_colors(Screen *self) {
|
||||||
|
colorprofile_push_dynamic_colors(self->color_profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
screen_pop_dynamic_colors(Screen *self) {
|
||||||
|
colorprofile_pop_dynamic_colors(self->color_profile);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
screen_handle_print(Screen *self, PyObject *msg) {
|
screen_handle_print(Screen *self, PyObject *msg) {
|
||||||
CALLBACK("handle_remote_print", "O", msg);
|
CALLBACK("handle_remote_print", "O", msg);
|
||||||
|
|||||||
@@ -139,6 +139,8 @@ void screen_erase_characters(Screen *self, unsigned int count);
|
|||||||
void screen_set_margins(Screen *self, unsigned int top, unsigned int bottom);
|
void screen_set_margins(Screen *self, unsigned int top, unsigned int bottom);
|
||||||
void screen_change_charset(Screen *, uint32_t to);
|
void screen_change_charset(Screen *, uint32_t to);
|
||||||
void screen_handle_cmd(Screen *, PyObject *cmd);
|
void screen_handle_cmd(Screen *, PyObject *cmd);
|
||||||
|
void screen_push_dynamic_colors(Screen *);
|
||||||
|
void screen_pop_dynamic_colors(Screen *);
|
||||||
void screen_handle_print(Screen *, PyObject *cmd);
|
void screen_handle_print(Screen *, PyObject *cmd);
|
||||||
void screen_designate_charset(Screen *, uint32_t which, uint32_t as);
|
void screen_designate_charset(Screen *, uint32_t which, uint32_t as);
|
||||||
void screen_use_latin1(Screen *, bool);
|
void screen_use_latin1(Screen *, bool);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import shlex
|
|||||||
from .config_data import to_layout_names
|
from .config_data import to_layout_names
|
||||||
from .constants import shell_path
|
from .constants import shell_path
|
||||||
from .layout import all_layouts
|
from .layout import all_layouts
|
||||||
|
from .utils import log_error
|
||||||
|
|
||||||
|
|
||||||
class Tab:
|
class Tab:
|
||||||
@@ -105,10 +106,18 @@ def parse_session(raw, opts):
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def create_session(opts, args=None, special_window=None, cwd_from=None, respect_cwd=False):
|
def create_session(opts, args=None, special_window=None, cwd_from=None, respect_cwd=False, default_session=None):
|
||||||
if args and args.session:
|
if args and args.session:
|
||||||
with open(args.session) as f:
|
with open(args.session) as f:
|
||||||
return parse_session(f.read(), opts)
|
return parse_session(f.read(), opts)
|
||||||
|
if default_session and default_session != 'none':
|
||||||
|
try:
|
||||||
|
with open(default_session) as f:
|
||||||
|
session_data = f.read()
|
||||||
|
except EnvironmentError:
|
||||||
|
log_error('Failed to read from session file, ignoring: {}'.format(default_session))
|
||||||
|
else:
|
||||||
|
return parse_session(session_data, opts)
|
||||||
ans = Session()
|
ans = Session()
|
||||||
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
|
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
|
||||||
ans.add_tab(opts)
|
ans.add_tab(opts)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
#define VS15 1275
|
#define VS15 1280
|
||||||
#define VS16 1276
|
#define VS16 1281
|
||||||
|
|
||||||
bool is_combining_char(char_type ch);
|
bool is_combining_char(char_type ch);
|
||||||
bool is_ignored_char(char_type ch);
|
bool is_ignored_char(char_type ch);
|
||||||
|
|||||||
@@ -266,8 +266,7 @@ def remove_socket_file(s, path=None):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def single_instance_unix(name):
|
def unix_socket_paths(name, ext='.lock'):
|
||||||
import socket
|
|
||||||
import tempfile
|
import tempfile
|
||||||
home = os.path.expanduser('~')
|
home = os.path.expanduser('~')
|
||||||
candidates = [tempfile.gettempdir(), home]
|
candidates = [tempfile.gettempdir(), home]
|
||||||
@@ -276,34 +275,39 @@ def single_instance_unix(name):
|
|||||||
candidates = [user_cache_dir(), '/Library/Caches']
|
candidates = [user_cache_dir(), '/Library/Caches']
|
||||||
for loc in candidates:
|
for loc in candidates:
|
||||||
if os.access(loc, os.W_OK | os.R_OK | os.X_OK):
|
if os.access(loc, os.W_OK | os.R_OK | os.X_OK):
|
||||||
filename = ('.' if loc == home else '') + name + '.lock'
|
filename = ('.' if loc == home else '') + name + ext
|
||||||
path = os.path.join(loc, filename)
|
yield os.path.join(loc, filename)
|
||||||
socket_path = path.rpartition('.')[0] + '.sock'
|
|
||||||
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC | os.O_CLOEXEC)
|
|
||||||
try:
|
def single_instance_unix(name):
|
||||||
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
import socket
|
||||||
except EnvironmentError as err:
|
for path in unix_socket_paths(name):
|
||||||
if err.errno in (errno.EAGAIN, errno.EACCES):
|
socket_path = path.rpartition('.')[0] + '.sock'
|
||||||
# Client
|
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC | os.O_CLOEXEC)
|
||||||
s = socket.socket(family=socket.AF_UNIX)
|
try:
|
||||||
s.connect(socket_path)
|
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
single_instance.socket = s
|
except EnvironmentError as err:
|
||||||
return False
|
if err.errno in (errno.EAGAIN, errno.EACCES):
|
||||||
raise
|
# Client
|
||||||
s = socket.socket(family=socket.AF_UNIX)
|
s = socket.socket(family=socket.AF_UNIX)
|
||||||
try:
|
s.connect(socket_path)
|
||||||
|
single_instance.socket = s
|
||||||
|
return False
|
||||||
|
raise
|
||||||
|
s = socket.socket(family=socket.AF_UNIX)
|
||||||
|
try:
|
||||||
|
s.bind(socket_path)
|
||||||
|
except EnvironmentError as err:
|
||||||
|
if err.errno in (errno.EADDRINUSE, errno.EEXIST):
|
||||||
|
os.unlink(socket_path)
|
||||||
s.bind(socket_path)
|
s.bind(socket_path)
|
||||||
except EnvironmentError as err:
|
else:
|
||||||
if err.errno in (errno.EADDRINUSE, errno.EEXIST):
|
raise
|
||||||
os.unlink(socket_path)
|
single_instance.socket = s # prevent garbage collection from closing the socket
|
||||||
s.bind(socket_path)
|
atexit.register(remove_socket_file, s, socket_path)
|
||||||
else:
|
s.listen()
|
||||||
raise
|
s.set_inheritable(False)
|
||||||
single_instance.socket = s # prevent garbage collection from closing the socket
|
return True
|
||||||
atexit.register(remove_socket_file, s, socket_path)
|
|
||||||
s.listen()
|
|
||||||
s.set_inheritable(False)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def single_instance(group_id=None):
|
def single_instance(group_id=None):
|
||||||
|
|||||||
1464
kitty/wcwidth-std.h
generated
1464
kitty/wcwidth-std.h
generated
File diff suppressed because it is too large
Load Diff
44
kitty_tests/tui.py
Normal file
44
kitty_tests/tui.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
|
||||||
|
from . import BaseTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestTUI(BaseTest):
|
||||||
|
|
||||||
|
def test_line_edit(self):
|
||||||
|
from kittens.tui.line_edit import LineEdit
|
||||||
|
le = LineEdit()
|
||||||
|
le.on_text('abcd', False)
|
||||||
|
self.ae(le.cursor_pos, 4)
|
||||||
|
for i in range(5):
|
||||||
|
self.assertTrue(le.left()) if i < 4 else self.assertFalse(le.left())
|
||||||
|
self.ae(le.cursor_pos, max(0, 3 - i))
|
||||||
|
self.ae(le.pending_bell, True)
|
||||||
|
le.clear()
|
||||||
|
le.on_text('abcd', False), le.home()
|
||||||
|
self.ae(le.cursor_pos, 0)
|
||||||
|
for i in range(5):
|
||||||
|
self.assertTrue(le.right()) if i < 4 else self.assertFalse(le.right())
|
||||||
|
self.ae(le.cursor_pos, min(4, i + 1))
|
||||||
|
self.ae(le.pending_bell, True)
|
||||||
|
le.clear()
|
||||||
|
le.on_text('abcd', False)
|
||||||
|
self.ae(le.current_input, 'abcd')
|
||||||
|
self.ae(le.cursor_pos, 4)
|
||||||
|
self.ae(le.split_at_cursor(), ('abcd', ''))
|
||||||
|
le.backspace()
|
||||||
|
self.ae(le.current_input, 'abc')
|
||||||
|
self.ae(le.cursor_pos, 3)
|
||||||
|
self.assertFalse(le.pending_bell)
|
||||||
|
le.backspace(num=2)
|
||||||
|
self.ae(le.current_input, 'a')
|
||||||
|
self.ae(le.cursor_pos, 1)
|
||||||
|
self.assertFalse(le.pending_bell)
|
||||||
|
le.backspace(num=2)
|
||||||
|
self.ae(le.current_input, '')
|
||||||
|
self.ae(le.cursor_pos, 0)
|
||||||
|
le.backspace()
|
||||||
|
self.assertTrue(le.pending_bell)
|
||||||
@@ -311,9 +311,9 @@ def run_upload(args):
|
|||||||
files = {
|
files = {
|
||||||
os.path.join(build_path, 'build', f.format(version)): desc
|
os.path.join(build_path, 'build', f.format(version)): desc
|
||||||
for f, desc in {
|
for f, desc in {
|
||||||
'linux/64/dist/kitty-{}-x86_64.txz': 'Linux amd64 binary bundle',
|
|
||||||
'linux/32/dist/kitty-{}-i686.txz': 'Linux x86 binary bundle',
|
|
||||||
'osx/dist/kitty-{}.dmg': 'macOS dmg',
|
'osx/dist/kitty-{}.dmg': 'macOS dmg',
|
||||||
|
'linux/64/sw/dist/kitty-{}-x86_64.txz': 'Linux amd64 binary bundle',
|
||||||
|
'linux/32/sw/dist/kitty-{}-i686.txz': 'Linux x86 binary bundle',
|
||||||
}.items()
|
}.items()
|
||||||
}
|
}
|
||||||
files[f'build/kitty-{version}.tar.xz'] = 'Source code'
|
files[f'build/kitty-{version}.tar.xz'] = 'Source code'
|
||||||
|
|||||||
12
setup.py
12
setup.py
@@ -543,10 +543,11 @@ def copy_man_pages(ddir):
|
|||||||
shutil.rmtree(os.path.join(mandir, 'man1'))
|
shutil.rmtree(os.path.join(mandir, 'man1'))
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
src = 'docs/_build/man'
|
src = os.path.join(base, 'docs/_build/man')
|
||||||
if not os.path.exists(src):
|
if not os.path.exists(src):
|
||||||
raise SystemExit('''\
|
raise SystemExit('''\
|
||||||
The kitty man page is missing. If you are building from git then run: make docs
|
The kitty man page is missing. If you are building from git then run:
|
||||||
|
make && make docs
|
||||||
(needs the sphinx documentation system to be installed)
|
(needs the sphinx documentation system to be installed)
|
||||||
''')
|
''')
|
||||||
shutil.copytree(src, os.path.join(mandir, 'man1'))
|
shutil.copytree(src, os.path.join(mandir, 'man1'))
|
||||||
@@ -559,10 +560,11 @@ def copy_html_docs(ddir):
|
|||||||
shutil.rmtree(htmldir)
|
shutil.rmtree(htmldir)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
src = 'docs/_build/html'
|
src = os.path.join(base, 'docs/_build/html')
|
||||||
if not os.path.exists(src):
|
if not os.path.exists(src):
|
||||||
raise SystemExit('''\
|
raise SystemExit('''\
|
||||||
The kitty html docs are missing. If you are building from git then run: make html
|
The kitty html docs are missing. If you are building from git then run:
|
||||||
|
make && make docs
|
||||||
(needs the sphinx documentation system to be installed)
|
(needs the sphinx documentation system to be installed)
|
||||||
''')
|
''')
|
||||||
shutil.copytree(src, htmldir)
|
shutil.copytree(src, htmldir)
|
||||||
@@ -788,7 +790,7 @@ def main():
|
|||||||
)
|
)
|
||||||
elif args.action == 'linux-package':
|
elif args.action == 'linux-package':
|
||||||
build(args, native_optimizations=False)
|
build(args, native_optimizations=False)
|
||||||
if not args.for_freeze:
|
if not os.path.exists(os.path.join(base, 'docs/_build/html')):
|
||||||
run_tool(['make', 'docs'])
|
run_tool(['make', 'docs'])
|
||||||
package(args)
|
package(args)
|
||||||
elif args.action == 'osx-bundle':
|
elif args.action == 'osx-bundle':
|
||||||
|
|||||||
Reference in New Issue
Block a user