v0.1.0 d9218d6

separators.go RawHistoryFolder

1package core
2
3import "github.com/bnema/neferwl/internal/ports"
4
5// Separator lines follow tmux (screen-redraw.c): tiles share one line per
6// side where they touch, owned by the left or top tile (Placement.Inset).
7// The focused tile lights the lines along its sides, corners included.
8// With exactly two tiles one line splits them, and each lights only its
9// half of it: the left or top tile the first half. With gaps each tile
10// has lines of its own and lights them whole. Floats get a full border
11// of their own, drawn with them (Separator.Window).
12//
13// Only what is on the output counts: lines at the output edge (a column
14// scrolled off beyond it) are not drawn, and only tiles on it count.
15
16var sides = [...]ports.Sides{ports.SideLeft, ports.SideRight, ports.SideTop, ports.SideBottom}
17
18// separators are the lines of a layout on output o, inactive ones first
19// so active ones draw over them. lit says whether the focused tile shows
20// focus (only on the focused output).
21func separators(ps []Placement, width, gap int, o Rect, lit bool) []ports.Separator {
22	if width <= 0 {
23		return nil
24	}
25	var out []ports.Separator
26	add := func(p *Placement, r Rect, active bool) {
27		if !p.Floating && (r.X < o.X+width || r.X+r.W > o.X+o.W-width) && r.H > r.W {
28			return // a vertical line at the output edge
29		}
30		var id WindowID
31		if p.Floating {
32			id = p.ID
33		}
34		out = append(out, ports.Separator{Rect: r, Active: active, Window: id})
35	}
36	var focused *Placement
37	tiles := 0
38	for i := range ps {
39		p := &ps[i]
40		if p.Hidden || p.Fullscreen || p.Rect.W <= 0 || p.Rect.H <= 0 {
41			continue
42		}
43		if p.Neighbors != 0 && p.Rect.X < o.X+o.W && p.Rect.X+p.Rect.W > o.X {
44			tiles++
45		}
46		for _, s := range sides {
47			if p.Inset&s != 0 {
48				add(p, strip(p.Rect, s, 0, width), false)
49			}
50		}
51		if p.Focused && lit {
52			focused = p
53		}
54	}
55	if focused == nil {
56		return out
57	}
58	f := focused
59	for _, s := range sides {
60		var line ports.Rect
61		switch {
62		case f.Neighbors&s != 0 && f.Inset&s != 0:
63			line = strip(f.Rect, s, 0, width)
64		case f.Neighbors&s != 0:
65			line = strip(f.Rect, s, width, width)
66		case f.Inset&s != 0:
67			// A float's own border.
68			add(f, strip(f.Rect, s, 0, width), true)
69			continue
70		default:
71			continue
72		}
73		line = closeCorners(line, *f, s, width)
74		// Two tiles share one line only without gaps.
75		if tiles == 2 && gap == 0 {
76			line = half(line, s)
77		}
78		add(f, line, true)
79	}
80	return out
81}
82
83// strip is the b-wide strip along side s of r: inside r when out is 0,
84// else just outside it (the neighbor's line).
85func strip(r Rect, s ports.Sides, out, b int) Rect {
86	b = min(b, max(r.W/2, out), max(r.H/2, out))
87	switch s {
88	case ports.SideLeft:
89		return Rect{X: r.X - out, Y: r.Y, W: b, H: r.H}
90	case ports.SideRight:
91		return Rect{X: r.X + r.W - b + out, Y: r.Y, W: b, H: r.H}
92	case ports.SideTop:
93		return Rect{X: r.X, Y: r.Y - out, W: r.W, H: b}
94	}
95	return Rect{X: r.X, Y: r.Y + r.H - b + out, W: r.W, H: b}
96}
97
98// closeCorners runs a line along side s of p b further at each end where
99// the crossing line lies outside p (a neighbor's), so lit lines meet.
100func closeCorners(line Rect, p Placement, s ports.Sides, b int) Rect {
101	outside := func(s ports.Sides) bool { return p.Neighbors&s != 0 && p.Inset&s == 0 }
102	if s == ports.SideLeft || s == ports.SideRight {
103		if outside(ports.SideTop) {
104			line.Y, line.H = line.Y-b, line.H+b
105		}
106		if outside(ports.SideBottom) {
107			line.H += b
108		}
109		return line
110	}
111	if outside(ports.SideLeft) {
112		line.X, line.W = line.X-b, line.W+b
113	}
114	if outside(ports.SideRight) {
115		line.W += b
116	}
117	return line
118}
119
120// half keeps the half of a line owned by the tile on side s of it: the
121// first half for the left or top tile, else the second.
122func half(r Rect, s ports.Sides) Rect {
123	switch s {
124	case ports.SideRight:
125		r.H /= 2
126	case ports.SideLeft:
127		r.Y, r.H = r.Y+r.H/2, r.H-r.H/2
128	case ports.SideBottom:
129		r.W /= 2
130	case ports.SideTop:
131		r.X, r.W = r.X+r.W/2, r.W-r.W/2
132	}
133	return r
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 26

Tags 10

↑↓ moveenter openesc close