main 5d6fa26

demo-hint-label RawHistoryFolder

1#!/usr/bin/env python3
2"""Work out the hint label of a link, to type it the way a user reads it.
3
4  demo-hint-label ocr
5      Reads the frame on screen now (the compositor's latest screenshot) with
6      tesseract and keeps the words and their boxes in /tmp/hint-words.json.
7      Run it while the page is still, before the hint bind, so that the
8      lookup after the bind needs no OCR.
9
10  demo-hint-label label <since-epoch> <word>...
11      Waits for the hint dump (NEFERWL_HINT_DEBUG=1) of the scan started after
12      <since-epoch>, finds the link target that shows all the words and prints
13      its label. Labels follow internal/core/hint.go: the targets whose centre
14      is on the output, sorted by top then left edge, one letter each from the
15      home-row alphabet, two letters past 26 targets.
16"""
17import csv, glob, io, json, os, re, shutil, subprocess, sys, time
18
19ALPHABET = "fjdkslaghrueiwoqptyvbcnxmz"
20STATE = os.environ.get("XDG_STATE_HOME", "/work/state")
21DUMPS = os.path.join(STATE, "neferwl", "hints")
22SHOT = "/work/shots/latest.png"
23WORDS = "/tmp/hint-words.json"
24
25
26def ocr():
27    shutil.copyfile(SHOT, "/tmp/hint-ocr.png")
28    out = subprocess.run(["tesseract", "/tmp/hint-ocr.png", "stdout", "--psm", "11", "tsv"],
29                         capture_output=True, text=True, check=True).stdout
30    words = []
31    for row in csv.DictReader(io.StringIO(out), delimiter="\t", quoting=csv.QUOTE_NONE):
32        text = re.sub(r"\W", "", row["text"] or "").lower()
33        if text and float(row["conf"]) > 30:
34            x, y, w, h = (int(row[k]) for k in ("left", "top", "width", "height"))
35            words.append([text, x + w / 2, y + h / 2])
36    with open(WORDS, "w") as f:
37        json.dump(words, f)
38
39
40def newest_dump(since):
41    deadline = time.time() + 15
42    while time.time() < deadline:
43        for path in sorted(glob.glob(os.path.join(DUMPS, "*.json")), reverse=True):
44            if os.path.getmtime(path) >= since:
45                return path
46        time.sleep(0.05)
47    sys.exit("no hint scan dump after the bind")
48
49
50def label(since, wanted):
51    with open(newest_dump(since)) as f:
52        meta = json.load(f)
53    with open(WORDS) as f:
54        words = json.load(f)
55    # Core keeps the targets whose centre is on the output, then sorts them.
56    width, height = meta["width"], meta["height"]
57    targets = [t for t in meta["targets"]
58               if 0 <= (t["rect"][0] + t["rect"][2]) / 2 < width and 0 <= (t["rect"][1] + t["rect"][3]) / 2 < height]
59    targets.sort(key=lambda t: (t["rect"][1], t["rect"][0]))
60    for i, t in enumerate(targets):
61        x0, y0, x1, y1 = t["rect"]
62        inside = {w for w, cx, cy in words if x0 <= cx < x1 and y0 <= cy < y1}
63        if t["kind"] == "link" and set(wanted) <= inside:
64            n = len(ALPHABET)
65            print("link %d of %d shows %s" % (i + 1, len(targets), " ".join(wanted)), file=sys.stderr)
66            print(ALPHABET[i] if len(targets) <= n else ALPHABET[i // n] + ALPHABET[i % n])
67            return
68    sys.exit("no link shows %s (%d targets, %d words read)" % (" ".join(wanted), len(targets), len(words)))
69
70
71if sys.argv[1:2] == ["ocr"]:
72    ocr()
73elif sys.argv[1:2] == ["label"] and len(sys.argv) > 3:
74    label(float(sys.argv[2]), [w.lower() for w in sys.argv[3:]])
75else:
76    sys.exit(__doc__)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close