fix: better suggestion and overlay movement on key presses (#12)

- support history suggestions for aliases
https://github.com/versenilvis/IRIS/commit/0ae25d05ce0faf8ff2eeee9aa3fbb5889d9ad511
- merge spec and history results
(https://github.com/versenilvis/IRIS/commit/315c94f12bb689ad6b39b1f0ce5e0bdef71f38d5)
- add 25ms pty throttle and optimize ctrl+l
(https://github.com/versenilvis/IRIS/commit/1e603d74c208be00f6b2cb79a628416455711c3a)
- align suggestion menu to col 1
(https://github.com/versenilvis/IRIS/commit/e307b210849af65276e93e1cff8895f3b54937ff)
- resolve zsh ipc fd mismatch and generate autocomplete hooks
(https://github.com/versenilvis/IRIS/pull/12/commits/2199607fad8122ec441ff4b99e562ca62bd948ff)
- fix: eliminate prompt duplication and freeze UI movement during menu
navigation
https://github.com/versenilvis/IRIS/pull/12/commits/6783989a49f0a68d01bad542588983a5c7b597a2
This commit is contained in:
VERSE
2026-07-01 16:11:38 +07:00
committed by GitHub
parent 69eee40248
commit aca3d1670c
8 changed files with 619 additions and 231 deletions
+88 -37
View File
@@ -30,7 +30,7 @@ func init() {
idMapCache = make(map[string]int)
}
func SearchHistory(query string) ([]HistResult, error) {
func SearchHistory(query string, aliases map[string]string) ([]HistResult, error) {
mu.Lock()
defer mu.Unlock()
@@ -143,68 +143,119 @@ func SearchHistory(query string) ([]HistResult, error) {
return results, nil
}
matches := searcherCache.SearchWithScores(query, &fuzzy.SearchOptions{Limit: 200})
var alternativeQueries []string
for name, target := range aliases {
if target != "" {
qLow := strings.ToLower(query)
tLow := strings.ToLower(target)
nLow := strings.ToLower(name)
// when query has a clear first word, only keep history entries that share the same first word
// this prevents e.g. curl commands from showing up when typing "git ..."
queryFirstWord := ""
if strings.IndexByte(query, ' ') != -1 {
if fields := strings.Fields(query); len(fields) > 0 {
queryFirstWord = strings.ToLower(fields[0])
if qLow == tLow {
alternativeQueries = append(alternativeQueries, name)
} else if strings.HasPrefix(qLow, tLow+" ") {
suffix := query[len(target):]
alternativeQueries = append(alternativeQueries, name+suffix)
}
if qLow == nLow {
alternativeQueries = append(alternativeQueries, target)
} else if strings.HasPrefix(qLow, nLow+" ") {
suffix := query[len(name):]
alternativeQueries = append(alternativeQueries, target+suffix)
}
}
}
var results []HistResult
for _, m := range matches {
if queryFirstWord != "" {
seenCmds := make(map[string]bool)
addMatches := func(q string) {
qLow := strings.ToLower(q)
queryFirstWord := ""
if strings.IndexByte(qLow, ' ') != -1 {
if fields := strings.Fields(qLow); len(fields) > 0 {
queryFirstWord = fields[0]
}
}
matches := searcherCache.SearchWithScores(q, &fuzzy.SearchOptions{Limit: 200})
for _, m := range matches {
if seenCmds[m.Str] {
continue
}
// filter results by command name match
firstWord := m.Str
if idx := strings.IndexByte(m.Str, ' '); idx != -1 {
firstWord = m.Str[:idx]
}
if !strings.EqualFold(firstWord, queryFirstWord) {
continue
firstWordLow := strings.ToLower(firstWord)
if queryFirstWord != "" {
if firstWordLow != queryFirstWord {
continue
}
} else {
if !strings.HasPrefix(firstWordLow, qLow) {
continue
}
}
seenCmds[m.Str] = true
results = append(results, HistResult{
ID: idMapCache[m.Str],
Cmd: m.Str,
FuzzyScore: m.Score,
})
}
results = append(results, HistResult{
ID: idMapCache[m.Str],
Cmd: m.Str,
FuzzyScore: m.Score,
})
}
// within the same tier, we sort by ID
// tier 1: exact match
// tier 2: prefix match
// tier 3: substring match
// tier 4: fuzzy match
addMatches(query)
for _, altQ := range alternativeQueries {
addMatches(altQ)
}
getTier := func(cmd, q string) int {
cmdLow := strings.ToLower(cmd)
qLow := strings.ToLower(q)
if cmdLow == qLow {
return 1
bestTier := 4
check := func(ql string) {
cmdLow := strings.ToLower(cmd)
qlLow := strings.ToLower(ql)
tier := 4
if cmdLow == qlLow {
tier = 1
} else if strings.HasPrefix(cmdLow, qlLow) {
tier = 2
} else if strings.Contains(cmdLow, qlLow) {
tier = 3
}
if tier < bestTier {
bestTier = tier
}
}
if strings.HasPrefix(cmdLow, qLow) {
return 2
check(q)
for _, altQ := range alternativeQueries {
check(altQ)
}
if strings.Contains(cmdLow, qLow) {
return 3
}
return 4
return bestTier
}
tiers := make([]int, len(results))
for i, r := range results {
tiers[i] = getTier(r.Cmd, query)
}
sort.SliceStable(results, func(i, j int) bool {
tI := getTier(results[i].Cmd, query)
tJ := getTier(results[j].Cmd, query)
tI := tiers[i]
tJ := tiers[j]
if tI != tJ {
return tI < tJ // lower tier is better
return tI < tJ
}
// If both are fuzzy matches (Tier 4), prioritize fuzzy score first!
if tI == 4 && results[i].FuzzyScore != results[j].FuzzyScore {
return results[i].FuzzyScore > results[j].FuzzyScore
}
// if same tier (and same fuzzy score), sort by ID descending (most recent first)
return results[i].ID > results[j].ID
})