feat: cache compiled regex

use mustcompile for regex.
Always use mustcompile which throws a panic
if the regex cannot be compiled.
We call recover to detect the panic and
display an error message to the user.
This commit is contained in:
lnu 2020-12-30 22:01:39 +01:00 committed by Jan De Dobbeleer
parent ee4b039e7a
commit 890d0ad0e1
5 changed files with 72 additions and 26 deletions

View file

@ -4,7 +4,6 @@ package main
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"syscall" "syscall"
"unsafe" "unsafe"
@ -48,10 +47,8 @@ func getWindowTitle(imageName, windowTitleRegex string) (string, error) {
} }
// returns the first window of the first pid // returns the first window of the first pid
_, windowTitle, err := GetWindowTitle(processPid[0], windowTitleRegex) _, windowTitle := GetWindowTitle(processPid[0], windowTitleRegex)
if err != nil {
return "", nil
}
return windowTitle, nil return windowTitle, nil
} }
@ -147,13 +144,10 @@ func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (length int
} }
// GetWindowTitle searches for a window attached to the pid // GetWindowTitle searches for a window attached to the pid
func GetWindowTitle(pid int, windowTitleRegex string) (syscall.Handle, string, error) { func GetWindowTitle(pid int, windowTitleRegex string) (syscall.Handle, string) {
var hwnd syscall.Handle var hwnd syscall.Handle
var title string var title string
compiledRegex, err := regexp.Compile(windowTitleRegex)
if err != nil {
return 0, "", fmt.Errorf("Error while compiling the regex '%s'", windowTitleRegex)
}
// callback fro EnumWindows // callback fro EnumWindows
cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr { cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
var prcsID int = 0 var prcsID int = 0
@ -168,7 +162,7 @@ func GetWindowTitle(pid int, windowTitleRegex string) (syscall.Handle, string, e
return 1 // continue enumeration return 1 // continue enumeration
} }
title = syscall.UTF16ToString(b) title = syscall.UTF16ToString(b)
if compiledRegex.MatchString(title) { if matchString(windowTitleRegex, title) {
// will cause EnumWindows to return 0 (error) // will cause EnumWindows to return 0 (error)
// but we don't want to enumerate all windows since we got what we want // but we don't want to enumerate all windows since we got what we want
hwnd = h hwnd = h
@ -183,5 +177,5 @@ func GetWindowTitle(pid int, windowTitleRegex string) (syscall.Handle, string, e
// it returns 0(error occurred) instead of 1(success) // it returns 0(error occurred) instead of 1(success)
// In our case, title will equal "" or the title of the window anyway // In our case, title will equal "" or the title of the window anyway
_ = EnumWindows(cb, 0) _ = EnumWindows(cb, 0)
return hwnd, title, nil return hwnd, title
} }

View file

@ -45,7 +45,7 @@ function global:Set-PoshGitStatus {
$config = $global:PoshSettings.Theme $config = $global:PoshSettings.Theme
$cleanPWD = $PWD.ProviderPath.TrimEnd("\") $cleanPWD = $PWD.ProviderPath.TrimEnd("\")
$cleanPSWD = $PWD.ToString().TrimEnd("\") $cleanPSWD = $PWD.ToString().TrimEnd("\")
$standardOut = @(&$omp --error="$errorCode" --pwd="$cleanPWD" --pswd="$cleanPSWD" --execution-time="$executionTime" --config="$config") $standardOut = @(&$omp --error="$errorCode" --pwd="$cleanPWD" --pswd="$cleanPSWD" --execution-time="$executionTime" --config="$config" 2>&1)
# Restore initial encoding # Restore initial encoding
[Console]::OutputEncoding = $originalOutputEncoding [Console]::OutputEncoding = $originalOutputEncoding
# the ouput can be multiline, joining these ensures proper rendering by adding line breaks with `n # the ouput can be multiline, joining these ensures proper rendering by adding line breaks with `n

View file

@ -1,9 +1,36 @@
package main package main
import "regexp" import (
"regexp"
"sync"
)
var (
regexCache map[string]*regexp.Regexp = make(map[string]*regexp.Regexp)
regexCacheLock = sync.Mutex{}
)
func getCompiledRegex(pattern string) *regexp.Regexp {
// try in cache first
re := regexCache[pattern]
if re != nil {
return re
}
// should we panic or return the error?
re = regexp.MustCompile(pattern)
// lock for concurrent access and save the compiled expression in cache
regexCacheLock.Lock()
regexCache[pattern] = re
regexCacheLock.Unlock()
return re
}
func findNamedRegexMatch(pattern, text string) map[string]string { func findNamedRegexMatch(pattern, text string) map[string]string {
re := regexp.MustCompile(pattern) // error ignored because mustCompile will cause a panic
re := getCompiledRegex(pattern)
match := re.FindStringSubmatch(text) match := re.FindStringSubmatch(text)
result := make(map[string]string) result := make(map[string]string)
if len(match) == 0 { if len(match) == 0 {
@ -19,7 +46,7 @@ func findNamedRegexMatch(pattern, text string) map[string]string {
} }
func findAllNamedRegexMatch(pattern, text string) []map[string]string { func findAllNamedRegexMatch(pattern, text string) []map[string]string {
re := regexp.MustCompile(pattern) re := getCompiledRegex(pattern)
match := re.FindAllStringSubmatch(text, -1) match := re.FindAllStringSubmatch(text, -1)
var results []map[string]string var results []map[string]string
if len(match) == 0 { if len(match) == 0 {
@ -40,6 +67,11 @@ func findAllNamedRegexMatch(pattern, text string) []map[string]string {
} }
func replaceAllString(pattern, text, replaceText string) string { func replaceAllString(pattern, text, replaceText string) string {
re := regexp.MustCompile(pattern) re := getCompiledRegex(pattern)
return re.ReplaceAllString(text, replaceText) return re.ReplaceAllString(text, replaceText)
} }
func matchString(pattern, text string) bool {
re := getCompiledRegex(pattern)
return re.MatchString(text)
}

View file

@ -3,7 +3,6 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"regexp"
) )
// Segment represent a single segment and it's configuration // Segment represent a single segment and it's configuration
@ -112,10 +111,8 @@ func (segment *Segment) shouldIgnoreFolder(cwd string) bool {
list := parseStringArray(value) list := parseStringArray(value)
for _, element := range list { for _, element := range list {
pattern := fmt.Sprintf("^%s$", element) pattern := fmt.Sprintf("^%s$", element)
matched, err := regexp.MatchString(pattern, cwd) matched := matchString(pattern, cwd)
if err == nil && matched { return matched
return true
}
} }
return false return false
} }
@ -163,6 +160,17 @@ func (segment *Segment) mapSegmentWithWriter(env environmentInfo) error {
} }
func (segment *Segment) setStringValue(env environmentInfo, cwd string) { func (segment *Segment) setStringValue(env environmentInfo, cwd string) {
defer func() {
err := recover()
if err == nil {
return
}
// display a message explaining omp failed(with the err)
message := fmt.Sprintf("oh-my-posh fatal error rendering %s segment:%s", segment.Type, err)
fmt.Println(message)
segment.stringValue = "error"
segment.active = true
}()
err := segment.mapSegmentWithWriter(env) err := segment.mapSegmentWithWriter(env)
if err != nil || segment.shouldIgnoreFolder(cwd) { if err != nil || segment.shouldIgnoreFolder(cwd) {
return return

View file

@ -84,8 +84,14 @@ func TestShouldIgnoreFolderRegexInverted(t *testing.T) {
IgnoreFolders: []string{"(?!Projects[\\/]).*"}, IgnoreFolders: []string{"(?!Projects[\\/]).*"},
}, },
} }
got := segment.shouldIgnoreFolder(cwd) // detect panic(thrown by MustCompile)
assert.False(t, got) defer func() {
if err := recover(); err != nil {
// display a message explaining omp failed(with the err)
assert.Equal(t, "regexp: Compile(`^(?!Projects[\\/]).*$`): error parsing regexp: invalid or unsupported Perl syntax: `(?!`", err)
}
}()
segment.shouldIgnoreFolder(cwd)
} }
func TestShouldIgnoreFolderRegexInvertedNonEscaped(t *testing.T) { func TestShouldIgnoreFolderRegexInvertedNonEscaped(t *testing.T) {
@ -94,6 +100,12 @@ func TestShouldIgnoreFolderRegexInvertedNonEscaped(t *testing.T) {
IgnoreFolders: []string{"(?!Projects/).*"}, IgnoreFolders: []string{"(?!Projects/).*"},
}, },
} }
got := segment.shouldIgnoreFolder(cwd) // detect panic(thrown by MustCompile)
assert.False(t, got) defer func() {
if err := recover(); err != nil {
// display a message explaining omp failed(with the err)
assert.Equal(t, "regexp: Compile(`^(?!Projects/).*$`): error parsing regexp: invalid or unsupported Perl syntax: `(?!`", err)
}
}()
segment.shouldIgnoreFolder(cwd)
} }