detached d5dd8bf

perf(hints): compile link patterns on first use

commit
d5dd8bfbffb6a8f4fc7b92a01d08103f72487d30
author
brice · · 2026-10-19 14:57 UTC
parent
9b0b5e5
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +15 -7
M internal/adapters/hints/ocr.go +15 -7
@@ -168,14 +168,22 @@ func readWords(ctx context.Context, img *image.Gray) ([]OCRWord, int, error) {
168168 return words, n, nil
169169 }
170170
171-var domainPattern = regexp.MustCompile(`(?i)^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+([a-z]{2,24})(?::[0-9]+)?(?:[/?#]\S*)?$`)
172-var schemePattern = regexp.MustCompile(`(?i)^(?:https?|htps|file|ftp):/{1,2}\S+$`)
173-var emailPattern = regexp.MustCompile(`(?i)^[a-z0-9.!#$%&'*+/=?^_` + "`" + `{|}~-]+@(?:[a-z0-9-]+\.)+[a-z]{2,24}$`)
174-var fileExtensions = strings.Fields("go md txt toml json yaml yml rs py js ts sh c h png jpg log conf lock mod sum html css")
171+// The link patterns compile on the first OCR link check, not at process
172+// start: with hint mode off nothing here costs memory or CPU.
173+var domainPattern = sync.OnceValue(func() *regexp.Regexp {
174+ return regexp.MustCompile(`(?i)^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+([a-z]{2,24})(?::[0-9]+)?(?:[/?#]\S*)?$`)
175+})
176+var schemePattern = sync.OnceValue(func() *regexp.Regexp { return regexp.MustCompile(`(?i)^(?:https?|htps|file|ftp):/{1,2}\S+$`) })
177+var emailPattern = sync.OnceValue(func() *regexp.Regexp {
178+ return regexp.MustCompile(`(?i)^[a-z0-9.!#$%&'*+/=?^_` + "`" + `{|}~-]+@(?:[a-z0-9-]+\.)+[a-z]{2,24}$`)
179+})
180+var fileExtensions = sync.OnceValue(func() []string {
181+ return strings.Fields("go md txt toml json yaml yml rs py js ts sh c h png jpg log conf lock mod sum html css")
182+})
175183
176184 func isTextLink(text string) bool {
177185 text = strings.Trim(text, "()[]<>.,;:'\"")
178- if schemePattern.MatchString(text) || (strings.HasPrefix(strings.ToLower(text), "www.") && len(text) > 4) || emailPattern.MatchString(text) {
186+ if schemePattern().MatchString(text) || (strings.HasPrefix(strings.ToLower(text), "www.") && len(text) > 4) || emailPattern().MatchString(text) {
179187 return true
180188 }
181189 host := text
@@ -185,11 +193,11 @@ func isTextLink(text string) bool {
185193 if ip := net.ParseIP(host); ip != nil && ip.To4() != nil {
186194 return true
187195 }
188- m := domainPattern.FindStringSubmatch(text)
196+ m := domainPattern().FindStringSubmatch(text)
189197 if m == nil {
190198 return false
191199 }
192- for _, ext := range fileExtensions {
200+ for _, ext := range fileExtensions() {
193201 if strings.EqualFold(m[1], ext) {
194202 return false
195203 }
@@ -168,14 +168,22 @@ func readWords(ctx context.Context, img *image.Gray) ([]OCRWord, int, error) {
168 return words, n, nil168 return words, n, nil
169 }169 }
170 170
171-var domainPattern = regexp.MustCompile(`(?i)^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+([a-z]{2,24})(?::[0-9]+)?(?:[/?#]\S*)?$`)171+// The link patterns compile on the first OCR link check, not at process
172-var schemePattern = regexp.MustCompile(`(?i)^(?:https?|htps|file|ftp):/{1,2}\S+$`)172+// start: with hint mode off nothing here costs memory or CPU.
173-var emailPattern = regexp.MustCompile(`(?i)^[a-z0-9.!#$%&'*+/=?^_` + "`" + `{|}~-]+@(?:[a-z0-9-]+\.)+[a-z]{2,24}$`)173+var domainPattern = sync.OnceValue(func() *regexp.Regexp {
174-var fileExtensions = strings.Fields("go md txt toml json yaml yml rs py js ts sh c h png jpg log conf lock mod sum html css")174+ return regexp.MustCompile(`(?i)^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+([a-z]{2,24})(?::[0-9]+)?(?:[/?#]\S*)?$`)
175+})
176+var schemePattern = sync.OnceValue(func() *regexp.Regexp { return regexp.MustCompile(`(?i)^(?:https?|htps|file|ftp):/{1,2}\S+$`) })
177+var emailPattern = sync.OnceValue(func() *regexp.Regexp {
178+ return regexp.MustCompile(`(?i)^[a-z0-9.!#$%&'*+/=?^_` + "`" + `{|}~-]+@(?:[a-z0-9-]+\.)+[a-z]{2,24}$`)
179+})
180+var fileExtensions = sync.OnceValue(func() []string {
181+ return strings.Fields("go md txt toml json yaml yml rs py js ts sh c h png jpg log conf lock mod sum html css")
182+})
175 183
176 func isTextLink(text string) bool {184 func isTextLink(text string) bool {
177 text = strings.Trim(text, "()[]<>.,;:'\"")185 text = strings.Trim(text, "()[]<>.,;:'\"")
178- if schemePattern.MatchString(text) || (strings.HasPrefix(strings.ToLower(text), "www.") && len(text) > 4) || emailPattern.MatchString(text) {186+ if schemePattern().MatchString(text) || (strings.HasPrefix(strings.ToLower(text), "www.") && len(text) > 4) || emailPattern().MatchString(text) {
179 return true187 return true
180 }188 }
181 host := text189 host := text
@@ -185,11 +193,11 @@ func isTextLink(text string) bool {
185 if ip := net.ParseIP(host); ip != nil && ip.To4() != nil {193 if ip := net.ParseIP(host); ip != nil && ip.To4() != nil {
186 return true194 return true
187 }195 }
188- m := domainPattern.FindStringSubmatch(text)196+ m := domainPattern().FindStringSubmatch(text)
189 if m == nil {197 if m == nil {
190 return false198 return false
191 }199 }
192- for _, ext := range fileExtensions {200+ for _, ext := range fileExtensions() {
193 if strings.EqualFold(m[1], ext) {201 if strings.EqualFold(m[1], ext) {
194 return false202 return false
195 }203 }

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 25

Tags 10

↑↓ moveenter openesc close