From 314dd97059454535b81cc915b23275bd1d0953a0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 19 Aug 2022 14:35:17 +0530 Subject: [PATCH] Command line tools shouldnt need to resolve documentation refs Instead use a new kitty+doc protocol and have kitty navigate to the docs. Uses a default open action for the protocol, which can be overriden by the user should they so desire. --- docs/open_actions.rst | 4 ++++ kitty/cli.py | 26 +++++++++++++++----------- kitty/open_actions.py | 26 +++++++++++++++++++++++--- kitty/utils.py | 7 +++++++ kitty_tests/check_build.py | 1 + 5 files changed, 50 insertions(+), 14 deletions(-) diff --git a/docs/open_actions.rst b/docs/open_actions.rst index 6332ad68b..b15b6b9f9 100644 --- a/docs/open_actions.rst +++ b/docs/open_actions.rst @@ -64,6 +64,10 @@ some special variables, documented below: ``FRAGMENT`` The fragment (unquoted), if any of the URL or the empty string. +``URL_PATH`` + The path, query and fragment portions of the URL, without any + unquoting. + .. note:: You can use the :opt:`action_alias` option just as in :file:`kitty.conf` to diff --git a/kitty/cli.py b/kitty/cli.py index acd586e33..bf541af41 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -11,12 +11,12 @@ from typing import ( ) from .cli_stub import CLIOptions -from .conf.types import resolve_ref from .conf.utils import resolve_config from .constants import ( appname, clear_handled_signals, defconf, is_macos, str_version, website_url ) from .options.types import Options as KittyOpts +from .types import run_once from .typing import BadLineType, TypedDict @@ -171,22 +171,26 @@ def doc(x: str) -> str: return website_url(x) +@run_once +def hostname() -> str: + import socket + return socket.gethostname() or 'localhost' + + +def ref_hyperlink(x: str, prefix: str = '') -> str: + t, q = text_and_target(x) + url = f'kitty+doc://{hostname()}/#ref={prefix}{q}' + return hyperlink_for_url(url, t) + + @role def ref(x: str) -> str: - t, q = text_and_target(x) - url = resolve_ref(q) - if url: - return hyperlink_for_url(url, t) - return t + return ref_hyperlink(x) @role def ac(x: str) -> str: - t, q = text_and_target(x) - url = resolve_ref(q) - if url: - return hyperlink_for_url(url, t) - return t + return ref_hyperlink(x, 'action-') OptionSpecSeq = List[Union[str, OptionDict]] diff --git a/kitty/open_actions.py b/kitty/open_actions.py index 2ea078366..9803a8b39 100644 --- a/kitty/open_actions.py +++ b/kitty/open_actions.py @@ -75,7 +75,7 @@ def parse(lines: Iterable[str]) -> Iterator[OpenAction]: entries.append((tuple(match_criteria), tuple(raw_actions))) with to_cmdline_implementation.filter_env_vars( - 'URL', 'FILE_PATH', 'FILE', 'FRAGMENT', + 'URL', 'FILE_PATH', 'FILE', 'FRAGMENT', 'URL_PATH', EDITOR=shlex.join(get_editor()), SHELL=shlex.join(resolved_shell(get_options())) ): @@ -97,7 +97,7 @@ def url_matches_criterion(purl: 'ParseResult', url: str, unquoted_path: str, mc: if mc.type == 'mime': import fnmatch - mt = guess_type(unquoted_path, allow_filesystem_access=True) + mt = guess_type(unquoted_path, allow_filesystem_access=purl.scheme in ('', 'file')) if not mt: return False mt = mt.lower() @@ -169,10 +169,16 @@ def actions_for_url_from_list(url: str, actions: Iterable[OpenAction]) -> Iterat except Exception: return path = unquote(purl.path) + up = purl.path + if purl.query: + up += f'?{purl.query}' + if purl.fragment: + up += f'#{purl.fragment}' env = { 'URL': url, 'FILE_PATH': path, + 'URL_PATH': up, 'FILE': posixpath.basename(path), 'FRAGMENT': unquote(purl.fragment) } @@ -215,6 +221,15 @@ def load_launch_actions() -> Tuple[OpenAction, ...]: return tuple(parse(f)) +@run_once +def default_open_actions() -> Tuple[OpenAction, ...]: + return tuple(parse('''\ +# Open kitty HTML docs links +protocol kitty+doc +action show_kitty_doc $URL_PATH + '''.splitlines())) + + @run_once def default_launch_actions() -> Tuple[OpenAction, ...]: SHELL = resolved_shell(get_options()) @@ -260,7 +275,12 @@ def actions_for_url(url: str, actions_spec: Optional[str] = None) -> Iterator[Ke actions = load_open_actions() else: actions = tuple(parse(actions_spec.splitlines())) - yield from actions_for_url_from_list(url, actions) + found = False + for action in actions_for_url_from_list(url, actions): + found = True + yield action + if not found: + yield from actions_for_url_from_list(url, default_open_actions()) def actions_for_launch(url: str) -> Iterator[KeyAction]: diff --git a/kitty/utils.py b/kitty/utils.py index db72d38cd..c28df6d9f 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -1074,8 +1074,15 @@ def safer_fork() -> int: def docs_url(which: str = '', local_docs_root: str = '') -> str: from urllib.parse import quote from .constants import local_docs, website_url + from .conf.types import resolve_ref ld = local_docs_root or local_docs() base, frag = which.partition('#')[::2] + base = base.strip('/') + if frag.startswith('ref='): + ref = frag[4:] + which = resolve_ref(ref, lambda x: x) + base, frag = which.partition('#')[::2] + base = base.strip('/') if ld: base = base or 'index' url = f'file://{ld}/' + quote(base) + '.html' diff --git a/kitty_tests/check_build.py b/kitty_tests/check_build.py index a9f282021..150f7b608 100644 --- a/kitty_tests/check_build.py +++ b/kitty_tests/check_build.py @@ -81,6 +81,7 @@ class TestBuild(BaseTest): self.ae(p(), 'file:///docs/index.html') self.ae(p('conf'), 'file:///docs/conf.html') self.ae(p('kittens/ssh#frag'), 'file:///docs/kittens/ssh.html#frag') + self.ae(p('#ref=confloc'), 'file:///docs/conf.html#confloc') def main() -> None: