mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-15 13:07:52 +02:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73db717a0c | ||
|
|
f1552c8354 | ||
|
|
fb87fc32f0 | ||
|
|
b04b0c670d | ||
|
|
b33845df8a | ||
|
|
12c9d8d8f8 | ||
|
|
fe97bbcbbf | ||
|
|
10533c3eba | ||
|
|
b4415c90f9 | ||
|
|
2970bbdf6f | ||
|
|
95a420534d | ||
|
|
be56a21574 | ||
|
|
3741a235fe | ||
|
|
523b6c4c2d | ||
|
|
c3b2300310 | ||
|
|
1c4a20d86f | ||
|
|
f5af475012 | ||
|
|
f149b74332 | ||
|
|
8cd51386cb | ||
|
|
fb2cca88df | ||
|
|
031d4dc85a | ||
|
|
bf66d1c0f5 | ||
|
|
0edfa88755 | ||
|
|
dba1f3bcbc | ||
|
|
e204cc8f12 |
2
.github/workflows/ci.py
vendored
2
.github/workflows/ci.py
vendored
@@ -40,7 +40,7 @@ def install_deps():
|
||||
if is_bundle:
|
||||
install_bundle()
|
||||
else:
|
||||
run('pip install Pillow pygments')
|
||||
run('pip3 install Pillow pygments')
|
||||
|
||||
|
||||
def build_kitty():
|
||||
|
||||
@@ -4,6 +4,18 @@ Changelog
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
|
||||
0.19.1 [2020-10-06]
|
||||
-------------------
|
||||
|
||||
- hints kitten: Add an ``ip`` type for easy selection of IP addresses
|
||||
(:pull:`3009`)
|
||||
|
||||
- Fix a regression that caused a segfault when using
|
||||
:opt:`scrollback_pager_history_size` and it needs to be expanded (:iss:`3011`)
|
||||
|
||||
- Fix update available notifications repeating (:pull:`3006`)
|
||||
|
||||
|
||||
0.19.0 [2020-10-04]
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ you use some editor other than vim, you should adjust the
|
||||
|
||||
Finally, add an alias to your shell's rc files to invoke the kitten as ``hg``::
|
||||
|
||||
alias hg='kitty +kitten hyperlink_grep'
|
||||
alias hg='kitty +kitten hyperlinked_grep'
|
||||
|
||||
|
||||
You can now run searches with::
|
||||
@@ -42,10 +42,17 @@ You can now run searches with::
|
||||
hg some-search-term
|
||||
|
||||
If you want to enable completion, for the kitten, you can delegate completion
|
||||
to rg. For ZSH, you do that with::
|
||||
to rg. For that, instead of using an alias create a simple wrapper script named
|
||||
:file:`hg` somewhere in your ``PATH``:
|
||||
|
||||
compdef _rg kitty +kitten hyperlinked_grep
|
||||
.. code-block:: sh
|
||||
|
||||
#!/bin/sh
|
||||
exec kitty +kitten hyperlinked_grep "$@"
|
||||
|
||||
Then, for example, for ZSH, add the following to :file:`.zshrc`::
|
||||
|
||||
compdef _rg hg
|
||||
|
||||
To learn more about kitty's powerful framework for customizing URL click
|
||||
actions, :doc:`see here <../open_actions>`.
|
||||
|
||||
@@ -250,12 +250,13 @@ The escape code has the form::
|
||||
|
||||
<OSC> 99 ; metadata ; payload <terminator>
|
||||
|
||||
Here ``<OSC>`` is :code:`<ESC>]` and ``<terminator>`` is :code:`<ESC><backslash>`. The
|
||||
metadata is a section of colon separated :code:`key=value` pairs. Every key
|
||||
must be a single character from the set :code:`[a-zA-Z]` and every value must
|
||||
be a character from the set :code:`a-zA-Z0-9-_/\+.,(){}[]*&^%$#@!`~`. The
|
||||
payload must be interpreted based on the metadata section. The two semi-colons
|
||||
*must* always be present even when no metadata is present.
|
||||
Here ``<OSC>`` is :code:`<ESC>]` and ``<terminator>`` is
|
||||
:code:`<ESC><backslash>`. The metadata is a section of colon separated
|
||||
:code:`key=value` pairs. Every key must be a single character from the set
|
||||
:code:`a-zA-Z` and every value must be a word consisting of characters from
|
||||
the set :code:`a-zA-Z0-9-_/\+.,(){}[]*&^%$#@!`~`. The payload must be
|
||||
interpreted based on the metadata section. The two semi-colons *must* always be
|
||||
present even when no metadata is present.
|
||||
|
||||
Before going into details, lets see how one can display a simple, single line
|
||||
notification from a shell script::
|
||||
@@ -297,7 +298,7 @@ was activated. This is controlled by the ``a`` key which takes a comma
|
||||
separated set of values, ``report`` and ``focus``. The value ``focus`` means
|
||||
focus the window from which the notification was issued and is the default.
|
||||
``report`` means send an escape code back to the application. The format of the
|
||||
escape code is::
|
||||
returned escape code is::
|
||||
|
||||
<OSC> 99 ; i=identifier ; <terminator>
|
||||
|
||||
@@ -322,7 +323,7 @@ to display it based on what it does understand.
|
||||
allow displaying custom icons on a notification, at all, it was decided to
|
||||
leave it out of the spec for the time being.
|
||||
|
||||
Similarly, features such a scheduled notifications could be added in future
|
||||
Similarly, features such as scheduled notifications could be added in future
|
||||
revisions.
|
||||
|
||||
|
||||
|
||||
@@ -529,7 +529,6 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
|
||||
*width = (int)frameRect.size.width;
|
||||
if (height)
|
||||
*height = (int)frameRect.size.height;
|
||||
|
||||
}
|
||||
|
||||
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
|
||||
|
||||
@@ -1595,10 +1595,10 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
||||
|
||||
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
|
||||
{
|
||||
if (numer != GLFW_DONT_CARE && denom != GLFW_DONT_CARE)
|
||||
[window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
|
||||
else
|
||||
if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE)
|
||||
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
||||
else
|
||||
[window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
See https://sw.kovidgoyal.net/kitty/graphics-protocol.html
|
||||
@@ -1 +0,0 @@
|
||||
See https://sw.kovidgoyal.net/kitty/key-encoding.html
|
||||
@@ -236,6 +236,11 @@ def postprocessor(func: PostprocessorFunc) -> PostprocessorFunc:
|
||||
return func
|
||||
|
||||
|
||||
class InvalidMatch(Exception):
|
||||
"""Raised when a match turns out to be invalid."""
|
||||
pass
|
||||
|
||||
|
||||
@postprocessor
|
||||
def url(text: str, s: int, e: int) -> Tuple[int, int]:
|
||||
if s > 4 and text[s - 5:s] == 'link:': # asciidoc URLs
|
||||
@@ -280,11 +285,29 @@ def quotes(text: str, s: int, e: int) -> Tuple[int, int]:
|
||||
return s, e
|
||||
|
||||
|
||||
@postprocessor
|
||||
def ip(text: str, s: int, e: int) -> Tuple[int, int]:
|
||||
from ipaddress import ip_address
|
||||
# Check validity of IPs (or raise InvalidMatch)
|
||||
ip = text[s:e]
|
||||
|
||||
try:
|
||||
ip_address(ip)
|
||||
except Exception:
|
||||
raise InvalidMatch("Invalid IP")
|
||||
|
||||
return s, e
|
||||
|
||||
|
||||
def mark(pattern: str, post_processors: Iterable[PostprocessorFunc], text: str, args: HintsCLIOptions) -> Generator[Mark, None, None]:
|
||||
pat = re.compile(pattern)
|
||||
for idx, (s, e, groupdict) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
|
||||
for func in post_processors:
|
||||
s, e = func(text, s, e)
|
||||
try:
|
||||
for func in post_processors:
|
||||
s, e = func(text, s, e)
|
||||
except InvalidMatch:
|
||||
continue
|
||||
|
||||
mark_text = text[s:e].replace('\n', '').replace('\0', '')
|
||||
yield Mark(idx, s, e, mark_text, groupdict)
|
||||
|
||||
@@ -325,6 +348,15 @@ def functions_for(args: HintsCLIOptions) -> Tuple[str, List[PostprocessorFunc]]:
|
||||
pattern = '(?m)^\\s*(.+)[\\s\0]*$'
|
||||
elif args.type == 'hash':
|
||||
pattern = '[0-9a-f]{7,128}'
|
||||
elif args.type == 'ip':
|
||||
pattern = (
|
||||
# # IPv4 with no validation
|
||||
r"((?:\d{1,3}\.){3}\d{1,3}"
|
||||
r"|"
|
||||
# # IPv6 with no validation
|
||||
r"(?:[a-fA-F0-9]{0,4}:){2,7}[a-fA-F0-9]{1,4})"
|
||||
)
|
||||
post_processors.append(ip)
|
||||
elif args.type == 'word':
|
||||
chars = args.word_characters
|
||||
if chars is None:
|
||||
@@ -402,7 +434,7 @@ def process_hyperlinks(text: str) -> Tuple[str, Tuple[Mark, ...]]:
|
||||
active_hyperlink_start_offset = 0
|
||||
idx += 1
|
||||
|
||||
def process_hyperlink(m: re.Match) -> str:
|
||||
def process_hyperlink(m: 're.Match') -> str:
|
||||
nonlocal removed_size, active_hyperlink_url, active_hyperlink_id, active_hyperlink_start_offset
|
||||
raw = m.group()
|
||||
start = m.start() - removed_size
|
||||
@@ -482,7 +514,7 @@ programs.
|
||||
|
||||
--type
|
||||
default=url
|
||||
choices=url,regex,path,line,hash,word,linenum,hyperlink
|
||||
choices=url,regex,path,line,hash,word,linenum,hyperlink,ip
|
||||
The type of text to search for. A value of :code:`linenum` is special, it looks
|
||||
for error messages using the pattern specified with :option:`--regex`, which
|
||||
must have the named groups, :code:`path` and :code:`line`. If not specified,
|
||||
|
||||
@@ -1563,6 +1563,7 @@ class Boss:
|
||||
for os_window_id in os_windows:
|
||||
self.default_bg_changed_for(os_window_id)
|
||||
|
||||
# Can be called with kitty -o "map f1 send_test_notification"
|
||||
def send_test_notification(self) -> None:
|
||||
from time import monotonic
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class Version(NamedTuple):
|
||||
|
||||
|
||||
appname: str = 'kitty'
|
||||
version: Version = Version(0, 19, 0)
|
||||
version: Version = Version(0, 19, 1)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
|
||||
@@ -78,8 +78,8 @@ free_pagerhist(HistoryBuf *self) {
|
||||
static inline bool
|
||||
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
|
||||
if (ph->buffer_size >= ph->max_sz) return false;
|
||||
size_t newsz = ph->buffer_size + MAX(1024u * 1024u, minsz);
|
||||
uint8_t *newbuf = PyMem_Malloc(MIN(ph->buffer_size + minsz, ph->max_sz));
|
||||
size_t newsz = MIN(ph->max_sz, ph->buffer_size + MAX(1024u * 1024u, minsz));
|
||||
uint8_t *newbuf = PyMem_Malloc(newsz);
|
||||
if (!newbuf) return false;
|
||||
size_t copied = MIN(ph->length, ph->buffer_size - ph->start);
|
||||
if (copied) memcpy(newbuf, ph->buffer + ph->start, copied);
|
||||
|
||||
@@ -90,7 +90,9 @@ def save_notification(version: Version) -> None:
|
||||
for version in sorted(notified_versions):
|
||||
n = notified_versions[version]
|
||||
lines.append('{},{},{}'.format(
|
||||
'.'.join(map(str, n.version)), n.time_of_last_notification, n.count))
|
||||
'.'.join(map(str, n.version)),
|
||||
n.time_of_last_notification,
|
||||
n.notification_count))
|
||||
atomic_save('\n'.join(lines).encode('utf-8'), version_notification_log())
|
||||
|
||||
|
||||
|
||||
@@ -30,3 +30,31 @@ class TestHints(BaseTest):
|
||||
t('link:{}[xxx]'.format(u), u)
|
||||
t('`xyz <{}>`_.'.format(u), u)
|
||||
t('<a href="{}">moo'.format(u), u)
|
||||
|
||||
def test_ip_hints(self):
|
||||
from kittens.hints.main import parse_hints_args, functions_for, mark, convert_text
|
||||
args = parse_hints_args(['--type', 'ip'])[0]
|
||||
pattern, post_processors = functions_for(args)
|
||||
|
||||
def create_marks(text, cols=60):
|
||||
text = convert_text(text, cols)
|
||||
return tuple(mark(pattern, post_processors, text, args))
|
||||
|
||||
testcases = (
|
||||
('100.64.0.0', ['100.64.0.0']),
|
||||
('2001:0db8:0000:0000:0000:ff00:0042:8329', ['2001:0db8:0000:0000:0000:ff00:0042:8329']),
|
||||
('2001:db8:0:0:0:ff00:42:8329', ['2001:db8:0:0:0:ff00:42:8329']),
|
||||
('2001:db8::ff00:42:8329', ['2001:db8::ff00:42:8329']),
|
||||
('2001:DB8::FF00:42:8329', ['2001:DB8::FF00:42:8329']),
|
||||
('0000:0000:0000:0000:0000:0000:0000:0001', ['0000:0000:0000:0000:0000:0000:0000:0001']),
|
||||
('::1', ['::1']),
|
||||
# Invalid IPs won't match
|
||||
('255.255.255.256', []),
|
||||
(':1', []),
|
||||
)
|
||||
|
||||
for testcase, expected in testcases:
|
||||
with self.subTest(testcase=testcase, expected=expected):
|
||||
marks = create_marks(testcase)
|
||||
ips = [m.text for m in marks]
|
||||
self.ae(ips, expected)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
See https://sw.kovidgoyal.net/kitty/protocol-extensions.html
|
||||
19
publish.py
19
publish.py
@@ -68,14 +68,15 @@ def run_html(args: Any) -> None:
|
||||
|
||||
def add_analytics() -> None:
|
||||
analytics = '''
|
||||
<!-- Google Analytics -->
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-20736318-2"></script>
|
||||
<script>
|
||||
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
|
||||
ga('create', 'UA-20736318-2', 'auto');
|
||||
ga('send', 'pageview');
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'UA-20736318-2');
|
||||
</script>
|
||||
<script async="async" src='https://www.google-analytics.com/analytics.js'></script>
|
||||
<!-- End Google Analytics -->\
|
||||
'''
|
||||
for dirpath, firnames, filenames in os.walk(publish_dir):
|
||||
for fname in filenames:
|
||||
@@ -87,6 +88,10 @@ ga('send', 'pageview');
|
||||
f.write(html.encode('utf-8'))
|
||||
|
||||
|
||||
def run_docs(args: Any) -> None:
|
||||
subprocess.check_call(['make', 'docs'])
|
||||
|
||||
|
||||
def run_website(args: Any) -> None:
|
||||
if os.path.exists(publish_dir):
|
||||
shutil.rmtree(publish_dir)
|
||||
@@ -384,6 +389,8 @@ def main() -> None:
|
||||
ans = 'n'
|
||||
if ans.lower() != 'y':
|
||||
return
|
||||
if actions == ['website']:
|
||||
actions.insert(0, 'docs')
|
||||
for action in actions:
|
||||
print('Running', action)
|
||||
cwd = os.getcwd()
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
See https://sw.kovidgoyal.net/kitty/remote-control.html
|
||||
2
setup.py
2
setup.py
@@ -1176,8 +1176,6 @@ def option_parser() -> argparse.ArgumentParser: # {{{
|
||||
|
||||
def main() -> None:
|
||||
global verbose
|
||||
if sys.version_info < (3, 5):
|
||||
raise SystemExit('python >= 3.5 required')
|
||||
args = option_parser().parse_args(namespace=Options())
|
||||
verbose = args.verbose > 0
|
||||
args.prefix = os.path.abspath(args.prefix)
|
||||
|
||||
Reference in New Issue
Block a user