Skip to content

Commit

Permalink
feat: cache compiled regex
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lnu authored and JanDeDobbeleer committed Jan 20, 2021
1 parent ee4b039 commit 890d0ad
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 26 deletions.
18 changes: 6 additions & 12 deletions src/environment_windows_win32.go
Expand Up @@ -4,7 +4,6 @@ package main

import (
"fmt"
"regexp"
"strings"
"syscall"
"unsafe"
Expand Down Expand Up @@ -48,10 +47,8 @@ func getWindowTitle(imageName, windowTitleRegex string) (string, error) {
}

// returns the first window of the first pid
_, windowTitle, err := GetWindowTitle(processPid[0], windowTitleRegex)
if err != nil {
return "", nil
}
_, windowTitle := GetWindowTitle(processPid[0], windowTitleRegex)

return windowTitle, nil
}

Expand Down Expand Up @@ -147,13 +144,10 @@ func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (length int
}

// 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 title string
compiledRegex, err := regexp.Compile(windowTitleRegex)
if err != nil {
return 0, "", fmt.Errorf("Error while compiling the regex '%s'", windowTitleRegex)
}

// callback fro EnumWindows
cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
var prcsID int = 0
Expand All @@ -168,7 +162,7 @@ func GetWindowTitle(pid int, windowTitleRegex string) (syscall.Handle, string, e
return 1 // continue enumeration
}
title = syscall.UTF16ToString(b)
if compiledRegex.MatchString(title) {
if matchString(windowTitleRegex, title) {
// will cause EnumWindows to return 0 (error)
// but we don't want to enumerate all windows since we got what we want
hwnd = h
Expand All @@ -183,5 +177,5 @@ func GetWindowTitle(pid int, windowTitleRegex string) (syscall.Handle, string, e
// it returns 0(error occurred) instead of 1(success)
// In our case, title will equal "" or the title of the window anyway
_ = EnumWindows(cb, 0)
return hwnd, title, nil
return hwnd, title
}
2 changes: 1 addition & 1 deletion src/init/omp.ps1
Expand Up @@ -45,7 +45,7 @@ function global:Set-PoshGitStatus {
$config = $global:PoshSettings.Theme
$cleanPWD = $PWD.ProviderPath.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
[Console]::OutputEncoding = $originalOutputEncoding
# the ouput can be multiline, joining these ensures proper rendering by adding line breaks with `n
Expand Down
40 changes: 36 additions & 4 deletions src/regex.go
@@ -1,9 +1,36 @@
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 {
re := regexp.MustCompile(pattern)
// error ignored because mustCompile will cause a panic
re := getCompiledRegex(pattern)
match := re.FindStringSubmatch(text)
result := make(map[string]string)
if len(match) == 0 {
Expand All @@ -19,7 +46,7 @@ func findNamedRegexMatch(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)
var results []map[string]string
if len(match) == 0 {
Expand All @@ -40,6 +67,11 @@ func findAllNamedRegexMatch(pattern, text string) []map[string]string {
}

func replaceAllString(pattern, text, replaceText string) string {
re := regexp.MustCompile(pattern)
re := getCompiledRegex(pattern)
return re.ReplaceAllString(text, replaceText)
}

func matchString(pattern, text string) bool {
re := getCompiledRegex(pattern)
return re.MatchString(text)
}
18 changes: 13 additions & 5 deletions src/segment.go
Expand Up @@ -3,7 +3,6 @@ package main
import (
"errors"
"fmt"
"regexp"
)

// Segment represent a single segment and it's configuration
Expand Down Expand Up @@ -112,10 +111,8 @@ func (segment *Segment) shouldIgnoreFolder(cwd string) bool {
list := parseStringArray(value)
for _, element := range list {
pattern := fmt.Sprintf("^%s$", element)
matched, err := regexp.MatchString(pattern, cwd)
if err == nil && matched {
return true
}
matched := matchString(pattern, cwd)
return matched
}
return false
}
Expand Down Expand Up @@ -163,6 +160,17 @@ func (segment *Segment) mapSegmentWithWriter(env environmentInfo) error {
}

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)
if err != nil || segment.shouldIgnoreFolder(cwd) {
return
Expand Down
20 changes: 16 additions & 4 deletions src/segment_test.go
Expand Up @@ -84,8 +84,14 @@ func TestShouldIgnoreFolderRegexInverted(t *testing.T) {
IgnoreFolders: []string{"(?!Projects[\\/]).*"},
},
}
got := segment.shouldIgnoreFolder(cwd)
assert.False(t, got)
// detect panic(thrown by MustCompile)
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) {
Expand All @@ -94,6 +100,12 @@ func TestShouldIgnoreFolderRegexInvertedNonEscaped(t *testing.T) {
IgnoreFolders: []string{"(?!Projects/).*"},
},
}
got := segment.shouldIgnoreFolder(cwd)
assert.False(t, got)
// detect panic(thrown by MustCompile)
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)
}

0 comments on commit 890d0ad

Please sign in to comment.