feat(spec): support tool aliases (#107)

closes #57

IRIS now automatically read and suggest internal configuration aliases
for Git (`.gitconfig`, `.git/config`) and Cargo (`.cargo/config.toml`)

- Added the `Provider` interface and extended registry for CLI tools
that define aliases
- Git: parse aliases via `git config --show-scope`, supporting raw
tab/space separation and scope differentiation
- Cargo: traverse the directory tree upwards to root to read the
`.cargo/config.toml` configuration
- 0ms hot-path caching: key caching is calculated entirely via `os.Stat`
mtime (no subprocess spawn) combined with a 2-second TTL window to
ensure 0ms latency when typing
- Lookup integration: display alias suggestions at the subcommand level
and automatically expand aliases as the user enters further arguments
(ignore shell pipeline commands starting with `!`)
This commit is contained in:
VERSE
2026-08-02 14:30:46 +07:00
committed by GitHub
parent 476ca3946b
commit 6faa2e3274
10 changed files with 732 additions and 2 deletions
+75 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/versenilvis/iris/integration/shell"
"github.com/versenilvis/iris/internal/logger"
"github.com/versenilvis/iris/spec/alias"
)
var (
@@ -51,6 +52,9 @@ func Lookup(input string) []Suggestion {
}
tokens := Tokenize(input)
originalTokens := append([]string(nil), tokens...)
originalPrefix := ""
expandedPrefix := ""
if len(tokens) == 1 && tokens[0] == "" {
return topLevelSuggestions("", aliases)
@@ -77,6 +81,44 @@ func Lookup(input string) []Suggestion {
}
}
// Expand tool aliases
isAliasExpanded := false
aliasExpansionEnd := 0
if len(tokens) > 2 {
rootCmdName := tokens[0]
if provider := alias.GetProvider(rootCmdName); provider != nil {
cwd := GetCWD()
toolAliases := provider.GetAliases(cwd)
subCmd := tokens[1]
for _, a := range toolAliases {
if a.Name == subCmd {
if strings.HasPrefix(a.Expansion, "!") {
return nil
}
aliasTokens := Tokenize(a.Expansion)
if len(aliasTokens) > 0 && aliasTokens[len(aliasTokens)-1] == "" {
aliasTokens = aliasTokens[:len(aliasTokens)-1]
}
if len(aliasTokens) > 0 && aliasTokens[0] == rootCmdName {
aliasTokens = aliasTokens[1:]
}
newTokens := make([]string, 0, len(tokens)-1+len(aliasTokens))
newTokens = append(newTokens, tokens[0])
newTokens = append(newTokens, aliasTokens...)
newTokens = append(newTokens, tokens[2:]...)
tokens = newTokens
isAliasExpanded = true
aliasExpansionEnd = len(aliasTokens)
expandedPrefix = strings.Join(tokens[:1+aliasExpansionEnd], " ")
if len(originalTokens) >= 2 {
originalPrefix = strings.Join(originalTokens[:2], " ")
}
break
}
}
}
}
if len(tokens) == 1 {
query := tokens[0]
results := topLevelSuggestions(query, aliases)
@@ -152,7 +194,7 @@ func Lookup(input string) []Suggestion {
continue
}
// invalid subcommand word typed
if len(currentSubs) > 0 {
if len(currentSubs) > 0 && (!isAliasExpanded || depth > aliasExpansionEnd) {
return nil
}
break
@@ -280,6 +322,30 @@ func Lookup(input string) []Suggestion {
})
}
}
if depth == 1 {
if provider := alias.GetProvider(rootCmdName); provider != nil {
cwd := GetCWD()
for _, a := range provider.GetAliases(cwd) {
if partial == "" || HasPrefix(a.Name, partial) {
priority := 70
switch a.Scope {
case "local", "worktree", "command":
priority = 85
case "system":
priority = 65
}
results = append(results, Suggestion{
Cmd: prefix + " " + a.Name,
Desc: "Alias for: " + a.Expansion,
Icon: rootCmdName,
Priority: priority,
Source: "alias",
})
}
}
}
}
}
usedOpts := make(map[string]bool)
@@ -305,6 +371,14 @@ func Lookup(input string) []Suggestion {
}
}
if isAliasExpanded && expandedPrefix != "" && originalPrefix != "" {
for i := range results {
if strings.HasPrefix(results[i].Cmd, expandedPrefix) {
results[i].Cmd = originalPrefix + strings.TrimPrefix(results[i].Cmd, expandedPrefix)
}
}
}
return results
}