Skip to content

Commit

Permalink
Diacritics substitution in prompt (#7205)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjlevesque committed Apr 21, 2023
1 parent 8b2cea1 commit 7cfbf47
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 0 deletions.
15 changes: 15 additions & 0 deletions internal/prompter/prompter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/AlecAivazis/survey/v2"
"github.com/cli/cli/v2/internal/ghinstance"
"github.com/cli/cli/v2/internal/text"
"github.com/cli/cli/v2/pkg/surveyext"
)

Expand Down Expand Up @@ -49,11 +50,24 @@ type surveyPrompter struct {
stderr io.Writer
}

// LatinMatchingFilter returns whether the value matches the input filter.
// The strings are compared normalized in case.
// The filter's diactritics are kept as-is, but the value's are normalized,
// so that a missing diactritic in the filter still returns a result.
func LatinMatchingFilter(filter, value string, index int) bool {
filter = strings.ToLower(filter)
value = strings.ToLower(value)

// include this option if it matches.
return strings.Contains(value, filter) || strings.Contains(text.RemoveDiacritics(value), filter)
}

func (p *surveyPrompter) Select(message, defaultValue string, options []string) (result int, err error) {
q := &survey.Select{
Message: message,
Options: options,
PageSize: 20,
Filter: LatinMatchingFilter,
}

if defaultValue != "" {
Expand All @@ -77,6 +91,7 @@ func (p *surveyPrompter) MultiSelect(message, defaultValue string, options []str
Message: message,
Options: options,
PageSize: 20,
Filter: LatinMatchingFilter,
}

if defaultValue != "" {
Expand Down
78 changes: 78 additions & 0 deletions internal/prompter/prompter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package prompter

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestFilterDiacritics(t *testing.T) {
tests := []struct {
name string
filter string
value string
want bool
}{
{
name: "exact match no diacritics",
filter: "Mikelis",
value: "Mikelis",
want: true,
},
{
name: "exact match no diacritics",
filter: "Mikelis",
value: "Mikelis",
want: true,
},
{
name: "exact match diacritics",
filter: "Miķelis",
value: "Miķelis",
want: true,
},
{
name: "partial match diacritics",
filter: "Miķe",
value: "Miķelis",
want: true,
},
{
name: "exact match diacritics in value",
filter: "Mikelis",
value: "Miķelis",
want: true,
},
{
name: "partial match diacritics in filter",
filter: "Miķe",
value: "Miķelis",
want: true,
},
{
name: "no match when removing diacritics in filter",
filter: "Mielis",
value: "Mikelis",
want: false,
},
{
name: "no match when removing diacritics in value",
filter: "Mikelis",
value: "Mielis",
want: false,
},
{
name: "no match diacritics in filter",
filter: "Miķelis",
value: "Mikelis",
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, LatinMatchingFilter(tt.filter, tt.value, 0), tt.want)
})
}

}
20 changes: 20 additions & 0 deletions internal/text/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import (
"regexp"
"strings"
"time"
"unicode"

"github.com/cli/go-gh/v2/pkg/text"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"golang.org/x/text/runes"
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
)

var whitespaceRE = regexp.MustCompile(`\s+`)
Expand Down Expand Up @@ -72,3 +76,19 @@ func DisplayURL(urlStr string) string {
}
return u.Hostname() + u.Path
}

// RemoveDiacritics returns the input value without "diacritics", or accent marks
func RemoveDiacritics(value string) string {
// Mn = "Mark, nonspacing" unicode character category
removeMnTransfomer := runes.Remove(runes.In(unicode.Mn))

// 1/ Decompose the text into characters and diacritical marks,
// 2/ Remove the diacriticals marks
// 3/ Recompose the text
t := transform.Chain(norm.NFD, removeMnTransfomer, norm.NFC)
normalized, _, err := transform.String(t, value)
if err != nil {
return value
}
return normalized
}
54 changes: 54 additions & 0 deletions internal/text/text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,57 @@ func TestFuzzyAgoAbbr(t *testing.T) {
assert.Equal(t, expected, fuzzy)
}
}

func TestRemoveDiacritics(t *testing.T) {
tests := [][]string{
// no diacritics
{"e", "e"},
{"و", "و"},
{"И", "И"},
{"ж", "ж"},
{"私", "私"},
{"万", "万"},

// diacritics test sets
{"à", "a"},
{"é", "e"},
{"è", "e"},
{"ô", "o"},
{"ᾳ", "α"},
{"εͅ", "ε"},
{"ῃ", "η"},
{"ιͅ", "ι"},

{"ؤ", "و"},

{"ā", "a"},
{"č", "c"},
{"ģ", "g"},
{"ķ", "k"},
{"ņ", "n"},
{"š", "s"},
{"ž", "z"},

{"ŵ", "w"},
{"ŷ", "y"},
{"ä", "a"},
{"ÿ", "y"},
{"á", "a"},
{"ẁ", "w"},
{"ỳ", "y"},
{"ō", "o"},

// full words
{"Miķelis", "Mikelis"},
{"François", "Francois"},
{"žluťoučký", "zlutoucky"},
{"învățătorița", "invatatorita"},
{"Kękę przy łóżku", "Keke przy łozku"},
}

for _, tt := range tests {
t.Run(RemoveDiacritics(tt[0]), func(t *testing.T) {
assert.Equal(t, tt[1], RemoveDiacritics(tt[0]))
})
}
}
2 changes: 2 additions & 0 deletions pkg/cmd/pr/shared/editable.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/internal/prompter"
"github.com/cli/cli/v2/pkg/set"
"github.com/cli/cli/v2/pkg/surveyext"
)
Expand Down Expand Up @@ -395,6 +396,7 @@ func multiSelectSurvey(message string, defaults, options []string) ([]string, er
Message: message,
Options: options,
Default: defaults,
Filter: prompter.LatinMatchingFilter,
}
err := survey.AskOne(q, &results)
return results, err
Expand Down

0 comments on commit 7cfbf47

Please sign in to comment.