mirror of
https://github.com/wavetermdev/inshellisense.git
synced 2026-08-05 13:43:30 -07:00
fix: ui tabbing for partial command completion
Signed-off-by: cpendery <cpendery@vt.edu>
This commit is contained in:
+37
-11
@@ -17,6 +17,7 @@ var (
|
||||
cmdDelimiter = regexp.MustCompile(`(\|\|)|(&&)|(;)`)
|
||||
lastSuggestionCmd = ""
|
||||
lastSuggestion = []Suggestion{}
|
||||
lastCmdRunes = 0
|
||||
)
|
||||
|
||||
func getOption(token string, options []model.Option) *model.Option {
|
||||
@@ -59,14 +60,34 @@ func getLongName(names []string) string {
|
||||
return longestName
|
||||
}
|
||||
|
||||
func getShortName(names []string) string {
|
||||
if len(names) == 0 {
|
||||
return ""
|
||||
}
|
||||
shortestName := names[0]
|
||||
for _, name := range names {
|
||||
if len(name) < len(shortestName) {
|
||||
shortestName = name
|
||||
}
|
||||
}
|
||||
return shortestName
|
||||
}
|
||||
|
||||
func getSubcommandDrivenRecommendation(spec model.Subcommand, persistentOptions []model.Option, partialCmd *commandToken) []Suggestion {
|
||||
suggestions := []Suggestion{}
|
||||
allOptions := append(spec.Options, persistentOptions...)
|
||||
if partialCmd != nil {
|
||||
switch spec.FilterStrategy {
|
||||
case model.FilterStrategyFuzzy:
|
||||
return fuzzyMatchSubcommands(partialCmd.token, spec.Subcommands)
|
||||
return append(
|
||||
fuzzyMatchSubcommands(partialCmd.token, spec.Subcommands),
|
||||
fuzzyMatchOptions(partialCmd.token, allOptions)...,
|
||||
)
|
||||
case model.FilterStrategyPrefix, model.FilterStrategyEmpty:
|
||||
return prefixMatchSubcommands(partialCmd.token, spec.Subcommands)
|
||||
return append(
|
||||
prefixMatchSubcommands(partialCmd.token, spec.Subcommands),
|
||||
prefixMatchOptions(partialCmd.token, allOptions)...,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,9 +97,9 @@ func getSubcommandDrivenRecommendation(spec model.Subcommand, persistentOptions
|
||||
Description: sub.Description,
|
||||
})
|
||||
}
|
||||
for _, op := range append(spec.Options, persistentOptions...) {
|
||||
for _, op := range allOptions {
|
||||
suggestions = append(suggestions, Suggestion{
|
||||
Name: getLongName(op.Name),
|
||||
Name: getShortName(op.Name),
|
||||
Description: op.Description,
|
||||
})
|
||||
}
|
||||
@@ -164,7 +185,7 @@ func handleArg(tokens []commandToken, args []model.Arg, spec model.Subcommand, p
|
||||
return handleArg(tokens[1:], args[1:], spec, persistentOptions)
|
||||
}
|
||||
|
||||
func loadSuggestions(cmd string) (suggestions []Suggestion) {
|
||||
func loadSuggestions(cmd string) (suggestions []Suggestion, charsInLastCmd int) {
|
||||
activeCmd := ParseCommand(cmd)
|
||||
if len(activeCmd) <= 0 {
|
||||
return
|
||||
@@ -173,17 +194,22 @@ func loadSuggestions(cmd string) (suggestions []Suggestion) {
|
||||
if !rootToken.complete {
|
||||
return
|
||||
}
|
||||
lastCmd := activeCmd[len(activeCmd)-1]
|
||||
charsInLastCmd = len(lastCmd.token)
|
||||
if lastCmd.complete {
|
||||
charsInLastCmd = 0
|
||||
}
|
||||
if spec, ok := specs.Specs[rootToken.token]; ok {
|
||||
return handleSubcommand(activeCmd[1:], spec, []model.Option{})
|
||||
return handleSubcommand(activeCmd[1:], spec, []model.Option{}), charsInLastCmd
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func LoadSuggestions(cmd string) []Suggestion {
|
||||
func LoadSuggestions(cmd string) ([]Suggestion, int) {
|
||||
if cmd == lastSuggestionCmd {
|
||||
return lastSuggestion
|
||||
return lastSuggestion, lastCmdRunes
|
||||
}
|
||||
suggestions := loadSuggestions(cmd)
|
||||
lastSuggestionCmd, lastSuggestion = cmd, suggestions
|
||||
return suggestions
|
||||
suggestions, lastRunes := loadSuggestions(cmd)
|
||||
lastSuggestionCmd, lastSuggestion, lastCmdRunes = cmd, suggestions, lastRunes
|
||||
return suggestions, lastRunes
|
||||
}
|
||||
|
||||
+34
-12
@@ -8,17 +8,22 @@ import (
|
||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||
)
|
||||
|
||||
func fuzzyMatchSubcommands(input string, subcommands []model.Subcommand) []Suggestion {
|
||||
type matchable interface {
|
||||
GetName() []string
|
||||
GetDescription() string
|
||||
}
|
||||
|
||||
func fuzzyMatch[M matchable](input string, targets []M) []Suggestion {
|
||||
type match struct {
|
||||
name string
|
||||
rank int
|
||||
subcommand model.Subcommand
|
||||
name string
|
||||
rank int
|
||||
item M
|
||||
}
|
||||
matchers := []match{}
|
||||
for _, sub := range subcommands {
|
||||
for _, item := range targets {
|
||||
bestName := ""
|
||||
bestNameRank := -1
|
||||
for _, n := range sub.Name {
|
||||
for _, n := range item.GetName() {
|
||||
rank := fuzzy.RankMatch(input, n)
|
||||
if rank > bestNameRank {
|
||||
bestName = n
|
||||
@@ -27,8 +32,9 @@ func fuzzyMatchSubcommands(input string, subcommands []model.Subcommand) []Sugge
|
||||
}
|
||||
if bestNameRank != -1 {
|
||||
matchers = append(matchers, match{
|
||||
name: bestName,
|
||||
subcommand: sub,
|
||||
name: bestName,
|
||||
item: item,
|
||||
rank: bestNameRank,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -39,20 +45,28 @@ func fuzzyMatchSubcommands(input string, subcommands []model.Subcommand) []Sugge
|
||||
for _, m := range matchers {
|
||||
results = append(results, Suggestion{
|
||||
Name: m.name,
|
||||
Description: m.subcommand.Description,
|
||||
Description: m.item.GetDescription(),
|
||||
})
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func prefixMatchSubcommands(input string, subcommands []model.Subcommand) []Suggestion {
|
||||
func fuzzyMatchSubcommands(input string, subcommands []model.Subcommand) []Suggestion {
|
||||
return fuzzyMatch[model.Subcommand](input, subcommands)
|
||||
}
|
||||
|
||||
func fuzzyMatchOptions(input string, options []model.Option) []Suggestion {
|
||||
return fuzzyMatch[model.Option](input, options)
|
||||
}
|
||||
|
||||
func prefixMatch[M matchable](input string, subcommands []M) []Suggestion {
|
||||
results := []Suggestion{}
|
||||
for _, sub := range subcommands {
|
||||
for _, n := range sub.Name {
|
||||
for _, n := range sub.GetName() {
|
||||
if strings.HasPrefix(n, input) {
|
||||
results = append(results, Suggestion{
|
||||
Name: n,
|
||||
Description: sub.Description,
|
||||
Description: sub.GetDescription(),
|
||||
})
|
||||
break
|
||||
}
|
||||
@@ -60,3 +74,11 @@ func prefixMatchSubcommands(input string, subcommands []model.Subcommand) []Sugg
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func prefixMatchSubcommands(input string, subcommands []model.Subcommand) []Suggestion {
|
||||
return prefixMatch[model.Subcommand](input, subcommands)
|
||||
}
|
||||
|
||||
func prefixMatchOptions(input string, options []model.Option) []Suggestion {
|
||||
return prefixMatch[model.Option](input, options)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,14 @@ type Subcommand struct {
|
||||
FilterStrategy FilterStrategy
|
||||
}
|
||||
|
||||
func (s Subcommand) GetName() []string {
|
||||
return s.Name
|
||||
}
|
||||
|
||||
func (s Subcommand) GetDescription() string {
|
||||
return s.Description
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
Name []string //single or array string, required
|
||||
Args []Arg //single or array Arg, optional
|
||||
@@ -17,6 +25,14 @@ type Option struct {
|
||||
ExclusiveOn []string
|
||||
}
|
||||
|
||||
func (o Option) GetName() []string {
|
||||
return o.Name
|
||||
}
|
||||
|
||||
func (o Option) GetDescription() string {
|
||||
return o.Description
|
||||
}
|
||||
|
||||
type Arg struct {
|
||||
Name string //single, optional
|
||||
Description string //single, optional
|
||||
|
||||
@@ -22,6 +22,7 @@ type Model struct {
|
||||
keyMap KeyMap
|
||||
windowWidth int
|
||||
windowHeight int
|
||||
runesToRemove int
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -71,8 +72,8 @@ func (m Model) HasActiveSuggestion() bool {
|
||||
return len(m.suggestions) > 0
|
||||
}
|
||||
|
||||
func (m Model) ActiveSuggestion() string {
|
||||
return m.suggestions[m.cursor].Name
|
||||
func (m Model) ActiveSuggestion() (string, int) {
|
||||
return m.suggestions[m.cursor].Name, m.runesToRemove
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg, command string, userInputCursorLocation int) Model {
|
||||
@@ -93,7 +94,7 @@ func (m Model) Update(msg tea.Msg, command string, userInputCursorLocation int)
|
||||
m.windowWidth = msg.Width
|
||||
}
|
||||
m.userInputCursorLocation = userInputCursorLocation
|
||||
m.suggestions = autocomplete.LoadSuggestions(command)
|
||||
m.suggestions, m.runesToRemove = autocomplete.LoadSuggestions(command)
|
||||
if len(m.suggestions) == 0 {
|
||||
m.cursor = 0
|
||||
}
|
||||
|
||||
@@ -49,8 +49,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if !m.suggestions.HasActiveSuggestion() {
|
||||
return m, nil
|
||||
}
|
||||
activeSuggestion := m.suggestions.ActiveSuggestion()
|
||||
s := m.textInput.Value() + activeSuggestion
|
||||
activeSuggestion, runesToRemove := m.suggestions.ActiveSuggestion()
|
||||
currentValue := m.textInput.Value()
|
||||
s := currentValue[:len(currentValue)-runesToRemove] + activeSuggestion + " "
|
||||
m.textInput.SetValue(s)
|
||||
m.textInput.SetCursor(len(s))
|
||||
return m, nil
|
||||
|
||||
Reference in New Issue
Block a user