fix: handle non-ascii characters command (#118)

closes #106

please read the conversation to understand this PR
tested on:
- Accented letters: é, ü, ñ
- Non-Latin scripts: Chinese characters (中文), Arabic (العربية)
- Symbols & Punctuation: ©, €, ™, —
- Emojis: 🚀, 😀

<img width="1312" height="976" alt="image"
src="https://github.com/user-attachments/assets/613f7e2b-3681-4a64-aeeb-e44f4276c8ee"
/>
This commit is contained in:
VERSE
2026-08-04 23:13:23 +07:00
committed by GitHub
parent 30619a8621
commit 75aa1fbe38
+26 -8
View File
@@ -7,6 +7,7 @@ import (
"sort" "sort"
"strings" "strings"
"sync" "sync"
"unicode/utf8"
"github.com/versenilvis/fuzzy" "github.com/versenilvis/fuzzy"
"github.com/versenilvis/iris/integration/shell" "github.com/versenilvis/iris/integration/shell"
@@ -28,12 +29,13 @@ func RecordSessionCommand(cmd string) {
if cmd == "" { if cmd == "" {
return return
} }
cmd = sanitizeUTF8(cmd)
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
sessionHistoryMu.Lock() sessionHistoryMu.Lock()
defer sessionHistoryMu.Unlock() defer sessionHistoryMu.Unlock()
if len(sessionHistory) > 0 && sessionHistory[len(sessionHistory)-1] == cmd { if len(sessionHistory) > 0 && sessionHistory[len(sessionHistory)-1] == cmd {
return return
} }
@@ -51,6 +53,21 @@ func init() {
idMapCache = make(map[string]int) idMapCache = make(map[string]int)
} }
func sanitizeUTF8(s string) string {
if utf8.ValidString(s) {
return s
}
var result strings.Builder
for _, r := range s {
if r == utf8.RuneError {
result.WriteRune('�')
} else {
result.WriteRune(r)
}
}
return result.String()
}
func SearchHistory(query string, aliases map[string]string) ([]HistResult, error) { func SearchHistory(query string, aliases map[string]string) ([]HistResult, error) {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
@@ -132,6 +149,7 @@ func SearchHistory(query string, aliases map[string]string) ([]HistResult, error
cmd = strings.TrimSpace(cmd) cmd = strings.TrimSpace(cmd)
if cmd != "" { if cmd != "" {
cmd = sanitizeUTF8(cmd)
allCmds = append(allCmds, cmd) allCmds = append(allCmds, cmd)
} }
} }
@@ -222,12 +240,12 @@ func SearchHistory(query string, aliases map[string]string) ([]HistResult, error
if len(words) == 0 { if len(words) == 0 {
words = []string{qLow} words = []string{qLow}
} }
for _, cmd := range historyCache { for _, cmd := range historyCache {
if seenCmds[cmd] { if seenCmds[cmd] {
continue continue
} }
cmdLow := strings.ToLower(cmd) cmdLow := strings.ToLower(cmd)
matchAll := true matchAll := true
for _, w := range words { for _, w := range words {
@@ -236,11 +254,11 @@ func SearchHistory(query string, aliases map[string]string) ([]HistResult, error
break break
} }
} }
if !matchAll { if !matchAll {
continue continue
} }
seenCmds[cmd] = true seenCmds[cmd] = true
results = append(results, HistResult{ results = append(results, HistResult{
ID: idMapCache[cmd], ID: idMapCache[cmd],
@@ -258,8 +276,8 @@ func SearchHistory(query string, aliases map[string]string) ([]HistResult, error
if seenCmds[m.Str] { if seenCmds[m.Str] {
continue continue
} }
// filter out extremely weak fuzzy matches (e.g. random garbage typing that // filter out extremely weak fuzzy matches (e.g. random garbage typing that
// loosely matches across a very long command) // loosely matches across a very long command)
if len(q) > 0 && m.Score/len(q) < 150 { if len(q) > 0 && m.Score/len(q) < 150 {
continue continue