v0.7.2 71ebe61

paragraph.go RawHistoryFolder

1package text
2
3import (
4	"errors"
5	"github.com/bnema/nefergui/internal/bidi"
6)
7
8type paragraphRange struct{ start, end int }
9
10// paragraphRanges excludes paragraph separators from shaping. CRLF is a
11// single separator but consumes two source runes, so Run offsets stay global.
12// The terminal empty range is a caret line only when the input ends in a
13// separator; the empty input retains Measure's existing zero-height behavior.
14func paragraphRanges(text []rune) ([]paragraphRange, bool) {
15	var out []paragraphRange
16	start := 0
17	split := false
18	for i := 0; i < len(text); i++ {
19		r := text[i]
20		if !paragraphSeparator(r) {
21			continue
22		}
23		out = append(out, paragraphRange{start, i})
24		split = true
25		if r == '\r' && i+1 < len(text) && text[i+1] == '\n' {
26			i++
27		}
28		start = i + 1
29	}
30	if split {
31		out = append(out, paragraphRange{start, len(text)})
32	}
33	return out, split
34}
35func paragraphSeparator(r rune) bool {
36	switch r {
37	case '\n', '\r', 0x001c, 0x001d, 0x001e, 0x0085, 0x2029:
38		return true
39	}
40	return false
41}
42
43func (e *Engine) measureParagraphs(text []rune, paragraphs []paragraphRange, r Request, width float64) (Layout, error) {
44	var result Layout
45	if r.Direction > bidi.RTL {
46		return result, errors.New("invalid paragraph direction")
47	}
48	for index, p := range paragraphs {
49		// Uncached: part is modified in place below.
50		part, err := e.measure(string(text[p.start:p.end]), r, width)
51		if err != nil {
52			return Layout{}, err
53		}
54		if index == 0 {
55			result.Direction = part.Direction
56			result.Baseline = part.Baseline
57			result.LineHeight = part.LineHeight
58		}
59		if len(part.Lines) == 0 {
60			part.Lines = []Line{{Direction: part.Direction, Height: part.LineHeight, Baseline: part.Baseline}}
61		}
62		for _, line := range part.Lines {
63			line.Baseline += result.Height
64			for i := range line.Runs {
65				run := &line.Runs[i]
66				run.Start += p.start
67				run.End += p.start
68				run.Y += result.Height
69				for j := range run.Glyphs {
70					run.Glyphs[j].Y += result.Height
71					run.Glyphs[j].Cluster += p.start
72				}
73			}
74			result.Lines = append(result.Lines, line)
75			if line.Width > result.Width {
76				result.Width = line.Width
77			}
78		}
79		result.Height += float64(len(part.Lines)) * part.LineHeight
80	}
81	return result, nil
82}
83
84// A layout's Direction refers to the first paragraph. Each Line has its own
85// Direction so subsequent auto-detected RTL paragraphs retain their basis.

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