feat/hints a471fa4

model.go RawHistoryFolder

1package atspi
2
3import "github.com/bnema/neferwl/internal/ports"
4
5// AT-SPI constants (atspi-constants.h). The role ids are the AtspiRole enum
6// values; the state ids are bit indices of the state set.
7const (
8	roleCheckBox      = 7
9	roleComboBox      = 11
10	roleDialog        = 16
11	roleFrame         = 23
12	roleMenuItem      = 35
13	rolePageTab       = 37
14	rolePushButton    = 43
15	roleRadioButton   = 44
16	roleToggleButton  = 62
17	roleWindow        = 69
18	roleLink          = 88
19	roleDocumentFrame = 82
20	roleDocumentWeb   = 95
21
22	stateActive  = 1
23	stateShowing = 25
24	stateVisible = 30
25
26	roleWords  = 5 // int32 words of a role set in a MatchRule
27	stateWords = 2 // int32 words of a state set in a MatchRule
28
29	// AtspiCollectionMatchType and AtspiCollectionSortOrder values.
30	matchAll       = 1
31	matchAny       = 2
32	sortCanonical  = 1
33	matchCount     = 500
34	maxDepth       = 30
35	maxWalkNodes   = 3000
36	maxFrames      = 32
37	sizeTolerance  = 40
38	maxMatchesRead = matchCount
39)
40
41// clickableRoles are the roles that become hint targets.
42var clickableRoles = []uint32{
43	roleLink, rolePushButton, roleToggleButton, roleCheckBox,
44	roleRadioButton, roleMenuItem, rolePageTab, roleComboBox,
45}
46
47// matchRule is the Collection query: showing and visible objects of the
48// clickable roles (only links when linksOnly), plus web documents for coverage.
49type matchRule struct{ linksOnly bool }
50
51func (r matchRule) roles() []uint32 {
52	if r.linksOnly {
53		return []uint32{roleLink, roleDocumentFrame, roleDocumentWeb}
54	}
55	return append(append([]uint32(nil), clickableRoles...), roleDocumentFrame, roleDocumentWeb)
56}
57
58// roleBits packs role ids into the bit set of a MatchRule: bit id%32 of word
59// id/32.
60func roleBits(ids []uint32) []int32 {
61	words := make([]int32, roleWords)
62	for _, id := range ids {
63		if w := int(id / 32); w < len(words) {
64			words[w] |= int32(uint32(1) << (id % 32))
65		}
66	}
67	return words
68}
69
70// stateBits packs state ids into the bit set of a MatchRule.
71func stateBits(ids ...uint32) []int32 {
72	words := make([]int32, stateWords)
73	for _, id := range ids {
74		if w := int(id / 32); w < len(words) {
75			words[w] |= int32(uint32(1) << (id % 32))
76		}
77	}
78	return words
79}
80
81func hasState(set uint64, state uint) bool { return set&(1<<state) != 0 }
82
83// isClickable reports whether role makes a target.
84func isClickable(role uint32, linksOnly bool) bool {
85	if linksOnly {
86		return role == roleLink
87	}
88	for _, r := range clickableRoles {
89		if r == role {
90			return true
91		}
92	}
93	return false
94}
95
96// isWindowRole reports whether role is a top-level window role.
97func isWindowRole(role uint32) bool {
98	return role == roleFrame || role == roleWindow || role == roleDialog
99}
100
101// kindOf maps a role to the hint kind: links are links, the rest buttons.
102func kindOf(role uint32) ports.HintKind {
103	if role == roleLink {
104		return ports.HintLink
105	}
106	return ports.HintButton
107}
108
109// frameInfo is a showing top-level window of an app.
110type frameInfo struct {
111	ref    ref
112	active bool
113	w, h   int // size of the frame, from its window extents
114}
115
116// matchFrames pairs the windows of one app with its frames, one to one. A
117// window is paired with a frame when the pairing is certain:
118//   - one window and one frame;
119//   - the focused window with the one ACTIVE frame; that frame is then
120//     reserved and no other window can take it;
121//   - otherwise the one unreserved frame whose size is within sizeTolerance
122//     of the window, provided no other unmatched window claims it as well.
123//
124// Ambiguous windows are left out. The result maps the index of a window in
125// wins to the index of its frame in frames.
126func matchFrames(wins []ports.HintWindow, frames []frameInfo) map[int]int {
127	out := map[int]int{}
128	if len(wins) == 1 && len(frames) == 1 {
129		out[0] = 0
130		return out
131	}
132	// Active matches first: they are reserved against the size matches.
133	active, n := -1, 0
134	for j, f := range frames {
135		if f.active {
136			active, n = j, n+1
137		}
138	}
139	reserved := make([]bool, len(frames))
140	done := make([]bool, len(wins))
141	if n == 1 {
142		focused := -1
143		count := 0
144		for i, w := range wins {
145			if w.Focused {
146				focused, count = i, count+1
147			}
148		}
149		if count == 1 {
150			out[focused] = active
151			reserved[active] = true
152			done[focused] = true
153		}
154	}
155	claims := make([][]int, len(wins)) // size candidates per window
156	owners := make([]int, len(frames)) // windows that size-match a frame
157	for i, w := range wins {
158		if done[i] {
159			continue
160		}
161		for j, f := range frames {
162			if !reserved[j] && absInt(f.w-w.Rect.W) <= sizeTolerance && absInt(f.h-w.Rect.H) <= sizeTolerance {
163				claims[i] = append(claims[i], j)
164				owners[j]++
165			}
166		}
167	}
168	for i := range wins {
169		if !done[i] && len(claims[i]) == 1 && owners[claims[i][0]] == 1 {
170			out[i] = claims[i][0]
171		}
172	}
173	return out
174}
175
176func absInt(v int) int {
177	if v < 0 {
178		return -v
179	}
180	return v
181}
182
183// element is a match with its extents.
184type element struct {
185	role       uint32
186	x, y, w, h int32 // window-relative
187}
188
189// toTarget converts an element of a window into a logical output-local
190// target: the element extents are relative to the top-level frame, whose own
191// window-relative origin is (fx, fy); the result is clipped to the window.
192// It reports false for an empty result.
193func toTarget(win ports.Rect, fx, fy int32, e element) (ports.HintTarget, bool) {
194	if e.w <= 0 || e.h <= 0 {
195		return ports.HintTarget{}, false
196	}
197	r := ports.Rect{X: win.X + int(e.x-fx), Y: win.Y + int(e.y-fy), W: int(e.w), H: int(e.h)}
198	x0, y0 := max(r.X, win.X), max(r.Y, win.Y)
199	x1, y1 := min(r.X+r.W, win.X+win.W), min(r.Y+r.H, win.Y+win.H)
200	if x1 <= x0 || y1 <= y0 {
201		return ports.HintTarget{}, false
202	}
203	return ports.HintTarget{Rect: ports.Rect{X: x0, Y: y0, W: x1 - x0, H: y1 - y0}, Kind: kindOf(e.role)}, true
204}
205
206func isDocument(role uint32) bool { return role == roleDocumentFrame || role == roleDocumentWeb }

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