v0.1.0 47aaa0f

levels.go RawHistoryFolder

1package bidi
2
3import "fmt"
4
5// BaseDirection selects the paragraph embedding level. Auto follows P2/P3.
6// nefergui: expose the existing upstream paragraph algorithm's resolved levels.
7type BaseDirection uint8
8
9const (
10	Auto BaseDirection = iota
11	LTR
12	RTL
13)
14
15// Levels returns resolved embedding levels after L1, one per input rune.
16// lineBreaks contains strictly increasing, nonempty, rune offsets, ending in len(text).
17// Removed X9 formatting codes receive a convenience level; call Reorder with
18// the same text to omit those codes from the visual indices.
19// nefergui: adapt upstream newParagraph/getLevels for per-line layout consumers.
20func Levels(text []rune, base BaseDirection, lineBreaks []int) ([]uint8, error) {
21	levels, _, err := Resolve(text, base, lineBreaks)
22	return levels, err
23}
24
25// Resolve returns the paragraph base level and the resolved levels after L1.
26// nefergui: surface existing levels without copying or reclassifying UAX#9 classes.
27func Resolve(text []rune, base BaseDirection, lineBreaks []int) ([]uint8, uint8, error) {
28	if base > RTL {
29		return nil, 0, fmt.Errorf("invalid bidi base %d", base)
30	}
31	if len(text) == 0 {
32		if len(lineBreaks) != 0 {
33			return nil, 0, fmt.Errorf("breaks on empty paragraph")
34		}
35		if base == RTL {
36			return nil, 1, nil
37		}
38		return nil, 0, nil
39	}
40	if len(lineBreaks) == 0 || lineBreaks[len(lineBreaks)-1] != len(text) {
41		return nil, 0, fmt.Errorf("line breaks must end at paragraph length")
42	}
43	prev := 0
44	for _, end := range lineBreaks {
45		if end <= prev || end > len(text) {
46			return nil, 0, fmt.Errorf("invalid line break %d", end)
47		}
48		prev = end
49	}
50	classes := make([]Class, len(text))
51	pairTypes := make([]bracketType, len(text))
52	pairValues := make([]rune, len(text))
53	for i, r := range text {
54		prop, _ := LookupRune(r)
55		classes[i] = prop.Class()
56		switch {
57		case prop.IsOpeningBracket():
58			pairTypes[i] = bpOpen
59			pairValues[i] = r
60			// nefergui: U+2329/232A are canonically equivalent to U+3008/3009.
61			if r == 0x2329 {
62				pairValues[i] = 0x3008
63			}
64		case prop.IsBracket():
65			pairTypes[i] = bpClose
66			// nefergui: canonicalize close brackets to the opening identifier
67			// required by upstream bracketPairer.matchOpener.
68			pairValues[i] = prop.reverseBracket(r)
69			if r == 0x232a {
70				pairValues[i] = 0x3008
71			}
72		}
73	}
74	embedding := implicitLevel
75	if base == LTR {
76		embedding = 0
77	} else if base == RTL {
78		embedding = 1
79	}
80	p, err := newParagraph(classes, pairTypes, pairValues, embedding)
81	if err != nil {
82		return nil, 0, err
83	}
84	// nefergui: exercise the upstream L2 entry point as a differential oracle;
85	// levels below are the same L1 result used by that entry point.
86	_ = p.getReordering(lineBreaks)
87	raw := p.getLevels(lineBreaks)
88	out := make([]uint8, len(raw))
89	for i, l := range raw {
90		out[i] = uint8(l)
91	}
92	return out, uint8(p.embeddingLevel), nil
93}
94
95// Reorder returns a visual-to-logical rune map per line using L2; X9-removed
96// formatting characters are excluded from the result. Levels must be the
97// per-line L1 output of Resolve. Consumers may group neighboring indices into
98// shaped runs while preserving the shaper's glyph order within each run.
99// nefergui: reuse upstream multiline L2 rather than infer levels from parity.
100func Reorder(text []rune, levels []uint8, breaks []int) ([][]int, error) {
101	if len(text) != len(levels) {
102		return nil, fmt.Errorf("level count mismatch")
103	}
104	if len(text) == 0 {
105		return nil, nil
106	}
107	if len(breaks) == 0 || breaks[len(breaks)-1] != len(text) {
108		return nil, fmt.Errorf("invalid breaks")
109	}
110	var out [][]int
111	start := 0
112	for _, end := range breaks {
113		if end <= start || end > len(text) {
114			return nil, fmt.Errorf("invalid break %d", end)
115		}
116		lv := make([]level, end-start)
117		for i, v := range levels[start:end] {
118			lv[i] = level(v)
119		}
120		// nefergui: call upstream's multiline L2 helper to keep its code live.
121		order := computeMultilineReordering(lv, []int{len(lv)})
122		line := make([]int, 0, len(order))
123		for _, i := range order {
124			r := text[start+i]
125			cls, _ := LookupRune(r)
126			if !isRemovedByX9(cls.Class()) {
127				line = append(line, start+i)
128			}
129		}
130		out = append(out, line)
131		start = end
132	}
133	return out, nil
134}

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 1

Tags 12

↑↓ moveenter openesc close