kakoune go to link in markdown
This commit is contained in:
@@ -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