Compare commits
1
Commits
74a16943d5
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
336beb7bf7 |
@@ -1,6 +1,15 @@
|
|||||||
|
define-command -hidden -override md-follow-link \
|
||||||
|
-docstring 'Folge dem Markdown-/Hugo-Link unter dem Cursor' %{
|
||||||
|
evaluate-commands %sh{
|
||||||
|
python3 "$kak_config/scripts/hugo-link.py" \
|
||||||
|
"$kak_buffile" "$kak_cursor_line" "$kak_cursor_column"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
define-command -hidden -override filetype-markdown %{
|
define-command -hidden -override filetype-markdown %{
|
||||||
set-option window tabstop 2
|
set-option window tabstop 2
|
||||||
set-option window indentwidth 2
|
set-option window indentwidth 2
|
||||||
|
map window goto d '<esc>: md-follow-link<ret>' -docstring 'Link folgen'
|
||||||
}
|
}
|
||||||
|
|
||||||
hook global WinSetOption filetype=markdown %{ filetype-markdown }
|
hook global WinSetOption filetype=markdown %{ filetype-markdown }
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Folge dem Markdown-/Hugo-internen Link unter dem Cursor.
|
||||||
|
|
||||||
|
Aufruf: hugo-link.py <buffile> <cursor_line> <cursor_column>
|
||||||
|
|
||||||
|
Gibt auf stdout genau ein Kakoune-Kommando aus:
|
||||||
|
* edit -existing '<pfad>' -> Zieldatei oeffnen
|
||||||
|
* nop %sh{ xdg-open ... } -> externe URL im Browser
|
||||||
|
* echo -markup '{Error}..' -> nichts gefunden / Fehler
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def kak_str(s):
|
||||||
|
"""String fuer Kakoune in einfachen Anfuehrungszeichen quoten."""
|
||||||
|
return "'" + s.replace("'", "''") + "'"
|
||||||
|
|
||||||
|
|
||||||
|
def emit_and_exit(cmd):
|
||||||
|
print(cmd)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def fail(msg):
|
||||||
|
emit_and_exit("echo -markup " + kak_str("{Error}" + msg))
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_NAMES = (
|
||||||
|
"hugo.toml", "hugo.yaml", "hugo.yml", "hugo.json",
|
||||||
|
"config.toml", "config.yaml", "config.yml", "config.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
# [text](ziel "optionaler Titel") bzw. [text](<ziel>)
|
||||||
|
LINK_RE = re.compile(r'\[[^\]]*\]\(\s*<?([^)>\s]+)>?(?:\s+"[^"]*")?\s*\)')
|
||||||
|
# <https://...> Autolink
|
||||||
|
AUTOLINK_RE = re.compile(r'<([a-zA-Z][\w+.-]*:[^>\s]+)>')
|
||||||
|
|
||||||
|
|
||||||
|
def find_site_root(start):
|
||||||
|
d = start
|
||||||
|
while True:
|
||||||
|
if any(os.path.exists(os.path.join(d, n)) for n in CONFIG_NAMES):
|
||||||
|
return d
|
||||||
|
if os.path.isdir(os.path.join(d, "content")):
|
||||||
|
return d
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def target_under_cursor(line, idx):
|
||||||
|
for regex in (LINK_RE, AUTOLINK_RE):
|
||||||
|
for m in regex.finditer(line):
|
||||||
|
if m.start() <= idx < m.end():
|
||||||
|
return m.group(1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def open_external(target):
|
||||||
|
emit_and_exit("nop %%sh{ setsid xdg-open %s >/dev/null 2>&1 & }"
|
||||||
|
% shlex.quote(target))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 4:
|
||||||
|
fail("interner Aufruffehler")
|
||||||
|
buffile, line_s, col_s = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||||
|
line_no, col = int(line_s), int(col_s)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(buffile, encoding="utf-8") as fh:
|
||||||
|
lines = fh.read().split("\n")
|
||||||
|
except OSError as exc:
|
||||||
|
fail("kann Buffer nicht lesen: %s" % exc)
|
||||||
|
|
||||||
|
if not 1 <= line_no <= len(lines):
|
||||||
|
fail("Cursor ausserhalb des Buffers")
|
||||||
|
line = lines[line_no - 1]
|
||||||
|
|
||||||
|
target = target_under_cursor(line, col - 1)
|
||||||
|
if target is None:
|
||||||
|
fail("kein Link unter dem Cursor")
|
||||||
|
|
||||||
|
# Fragment (#...) und Query (?...) abtrennen
|
||||||
|
target = re.split(r'[#?]', target, maxsplit=1)[0]
|
||||||
|
if not target:
|
||||||
|
fail("Link zeigt nur auf einen Anker")
|
||||||
|
|
||||||
|
if (re.match(r'^[a-zA-Z][a-zA-Z0-9+.-]*://', target)
|
||||||
|
or target.startswith("//")
|
||||||
|
or target.startswith("mailto:")):
|
||||||
|
open_external(target)
|
||||||
|
|
||||||
|
buf_dir = os.path.dirname(os.path.abspath(buffile))
|
||||||
|
|
||||||
|
if target.startswith("/"):
|
||||||
|
root = find_site_root(buf_dir)
|
||||||
|
if root is None:
|
||||||
|
fail("kein Hugo-Root (hugo.toml / config.*) oberhalb gefunden")
|
||||||
|
content_dir = os.path.join(root, "content")
|
||||||
|
if not os.path.isdir(content_dir):
|
||||||
|
content_dir = root
|
||||||
|
base = os.path.normpath(
|
||||||
|
os.path.join(content_dir, target.lstrip("/")))
|
||||||
|
else:
|
||||||
|
base = os.path.normpath(os.path.join(buf_dir, target))
|
||||||
|
|
||||||
|
if base.endswith(".md"):
|
||||||
|
candidates = [base]
|
||||||
|
else:
|
||||||
|
candidates = [
|
||||||
|
base + ".md",
|
||||||
|
os.path.join(base, "_index.md"),
|
||||||
|
os.path.join(base, "index.md"),
|
||||||
|
base, # z.B. direkt verlinktes Bild / statische Datei
|
||||||
|
]
|
||||||
|
|
||||||
|
for cand in candidates:
|
||||||
|
if os.path.isfile(cand):
|
||||||
|
emit_and_exit("edit -existing " + kak_str(cand))
|
||||||
|
|
||||||
|
fail("Ziel nicht gefunden: " + " | ".join(candidates))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user