mirror of
https://github.com/wavetermdev/inshellisense.git
synced 2026-08-05 13:43:30 -07:00
feat: first full pass (w/ broken ui) working
Signed-off-by: cpendery <cpendery@vt.edu>
This commit is contained in:
+177
-2
@@ -1,9 +1,10 @@
|
||||
package autocomplete
|
||||
|
||||
import (
|
||||
"log"
|
||||
"log/slog"
|
||||
"regexp"
|
||||
|
||||
"github.com/cpendery/clac/autocomplete/model"
|
||||
"github.com/cpendery/clac/autocomplete/specs"
|
||||
)
|
||||
|
||||
@@ -18,6 +19,177 @@ var (
|
||||
lastSuggestion = []Suggestion{}
|
||||
)
|
||||
|
||||
func getOption(token string, options []model.Option) *model.Option {
|
||||
for _, option := range options {
|
||||
for _, optionName := range option.Name {
|
||||
if token == optionName {
|
||||
return &option
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSubcommand(token string, spec model.Subcommand) *model.Subcommand {
|
||||
for _, subcommand := range spec.Subcommands {
|
||||
for _, subcommandName := range subcommand.Name {
|
||||
if token == subcommandName {
|
||||
return &subcommand
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func argsAreOptional(args []model.Arg) bool {
|
||||
allOptional := true
|
||||
for _, arg := range args {
|
||||
allOptional = allOptional && arg.IsOptional
|
||||
}
|
||||
return allOptional
|
||||
}
|
||||
|
||||
/* <has a> <no a>
|
||||
cmd --flag arg --flag
|
||||
|
||||
thus load suggestions for the base command, which would be any template, suggestions, options, subcommand, or an arg
|
||||
|
||||
rules:
|
||||
if there is an arg, just show the description of that arg
|
||||
else, show suggestions, subcommand, template, options
|
||||
in that org
|
||||
*/
|
||||
|
||||
/*
|
||||
generally, we want to make a recommendation based on what we finish on, so that is what matters, we can tree down until we get there
|
||||
|
||||
subcommand
|
||||
if isOption -> option
|
||||
else
|
||||
if match subcommand -> subcommand
|
||||
else -> arg
|
||||
|
||||
if _ -> recommend based self + rest
|
||||
|
||||
option
|
||||
if hasArgs
|
||||
if args optional
|
||||
if next is subcommand || option -> go there
|
||||
if args variadic ---> arg
|
||||
else -> arg
|
||||
|
||||
if _ ->
|
||||
if args optional -> recommend on parent subcommand + rest + args
|
||||
else if has args -> recommend based on args
|
||||
else -> recommend based on parent subcommand + rest
|
||||
|
||||
arg
|
||||
<NEEDS_WORK>
|
||||
if _ -> recommend based on self
|
||||
*/
|
||||
|
||||
func getLongName(names []string) string {
|
||||
longestName := ""
|
||||
for _, name := range names {
|
||||
if len(name) > len(longestName) {
|
||||
longestName = name
|
||||
}
|
||||
}
|
||||
return longestName
|
||||
}
|
||||
|
||||
func getSubcommandDrivenRecommendation(spec model.Subcommand, persistentOptions []model.Option) []Suggestion {
|
||||
suggestions := []Suggestion{}
|
||||
for _, sub := range spec.Subcommands {
|
||||
suggestions = append(suggestions, Suggestion{
|
||||
Name: getLongName(sub.Name),
|
||||
Description: sub.Description,
|
||||
})
|
||||
}
|
||||
for _, op := range append(spec.Options, persistentOptions...) {
|
||||
suggestions = append(suggestions, Suggestion{
|
||||
Name: getLongName(op.Name),
|
||||
Description: op.Description,
|
||||
})
|
||||
}
|
||||
return suggestions
|
||||
}
|
||||
|
||||
func getArgDrivenRecommendation(args []model.Arg, spec model.Subcommand, persistentOptions []model.Option) []Suggestion {
|
||||
return []Suggestion{}
|
||||
}
|
||||
|
||||
func handleSubcommand(tokens []commandToken, spec model.Subcommand, persistentOptions []model.Option) (suggestions []Suggestion) {
|
||||
if len(tokens) == 0 {
|
||||
return getSubcommandDrivenRecommendation(spec, persistentOptions)
|
||||
}
|
||||
for _, option := range spec.Options {
|
||||
if option.IsPersistent {
|
||||
persistentOptions = append(persistentOptions, option)
|
||||
}
|
||||
}
|
||||
activeCmd := tokens[0]
|
||||
if activeCmd.isOption {
|
||||
if option := getOption(activeCmd.token, append(spec.Options, persistentOptions...)); option != nil {
|
||||
return handleOption(tokens, *option, spec, persistentOptions)
|
||||
}
|
||||
return
|
||||
}
|
||||
if subcommand := getSubcommand(activeCmd.token, spec); subcommand != nil {
|
||||
return handleSubcommand(tokens[1:], *subcommand, persistentOptions)
|
||||
}
|
||||
|
||||
return handleArg(tokens, spec.Args, spec, persistentOptions)
|
||||
}
|
||||
|
||||
func handleOption(tokens []commandToken, option model.Option, spec model.Subcommand, persistentOptions []model.Option) (suggestions []Suggestion) {
|
||||
if len(tokens) == 0 {
|
||||
slog.Error("invalid state reached, option with no tokens")
|
||||
return
|
||||
}
|
||||
if len(option.Args) == 0 {
|
||||
return handleSubcommand(tokens[1:], spec, persistentOptions)
|
||||
}
|
||||
return handleArg(tokens[1:], option.Args, spec, persistentOptions)
|
||||
}
|
||||
|
||||
func handleArg(tokens []commandToken, args []model.Arg, spec model.Subcommand, persistentOptions []model.Option) (suggestions []Suggestion) {
|
||||
if len(tokens) == 0 {
|
||||
return getArgDrivenRecommendation(args, spec, persistentOptions)
|
||||
} else if len(args) == 0 {
|
||||
return handleSubcommand(tokens, spec, persistentOptions)
|
||||
}
|
||||
|
||||
activeCmd := tokens[0]
|
||||
if argsAreOptional(args) {
|
||||
if activeCmd.isOption {
|
||||
if option := getOption(activeCmd.token, append(spec.Options, persistentOptions...)); option != nil {
|
||||
return handleOption(tokens, *option, spec, persistentOptions)
|
||||
}
|
||||
return
|
||||
}
|
||||
subcommand := getSubcommand(activeCmd.token, spec)
|
||||
if subcommand != nil {
|
||||
return handleSubcommand(tokens[1:], *subcommand, persistentOptions)
|
||||
}
|
||||
}
|
||||
|
||||
activeArg := args[0]
|
||||
if activeArg.IsVariadic {
|
||||
return []Suggestion{{Name: activeArg.Name}}
|
||||
} else if activeArg.IsCommand {
|
||||
if len(tokens) <= 1 {
|
||||
return
|
||||
}
|
||||
activeCmd = tokens[1]
|
||||
if subcommand := getSubcommand(activeCmd.token, spec); subcommand != nil {
|
||||
return handleSubcommand(tokens[2:], *subcommand, persistentOptions)
|
||||
}
|
||||
return
|
||||
}
|
||||
return handleArg(tokens[1:], args[1:], spec, persistentOptions)
|
||||
}
|
||||
|
||||
func loadSuggestions(cmd string) (suggestions []Suggestion) {
|
||||
activeCmd := ParseCommand(cmd)
|
||||
if len(activeCmd) <= 0 {
|
||||
@@ -27,8 +199,11 @@ func loadSuggestions(cmd string) (suggestions []Suggestion) {
|
||||
if !rootToken.complete {
|
||||
return
|
||||
}
|
||||
if !activeCmd[len(activeCmd)-1].complete {
|
||||
return
|
||||
}
|
||||
if spec, ok := specs.Specs[rootToken.token]; ok {
|
||||
log.Println(spec.Description)
|
||||
return handleSubcommand(activeCmd[1:], spec, []model.Option{})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -15,7 +15,27 @@ const clearAllNewlines = (input: string): string => {
|
||||
};
|
||||
|
||||
const escapeSpecialCharacters = (input: string): string => {
|
||||
return input.replaceAll("`", '"').replaceAll("\0", "\\\\0");
|
||||
const chars = [...input];
|
||||
const special = new Set(["a", "b", "f", "n", "r", "t", "v", "\\", "0", ":"]);
|
||||
for (let i = 0; i < chars.length; i++) {
|
||||
switch (chars[i]) {
|
||||
case "\\":
|
||||
if (special.has(chars.at(i + 1))) {
|
||||
chars[i] = "\\\\";
|
||||
}
|
||||
if (chars.at(i + 1) == "\\") {
|
||||
chars[i + 1] = "\\\\";
|
||||
}
|
||||
break;
|
||||
case "`":
|
||||
chars[i] = '"';
|
||||
break;
|
||||
case "\0":
|
||||
chars[i] = "\\\\0";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return chars.join("");
|
||||
};
|
||||
|
||||
function chunk<T>(arr: T[], chuckSize: number): T[][] {
|
||||
@@ -114,7 +134,12 @@ const genName = (name: Fig.SingleOrArray<string> | undefined): string => {
|
||||
.filter((n) => n != null)
|
||||
.map((n) => `"${n}"`)
|
||||
.join(",")}},`
|
||||
: `Name: "${name}",`;
|
||||
: `Name: []string{"${name}"},`;
|
||||
};
|
||||
|
||||
const genSingleName = (name: string | undefined): string => {
|
||||
if (name == null) return "";
|
||||
return `Name: "${name}",`;
|
||||
};
|
||||
|
||||
const genDescription = (description: string | undefined): string => {
|
||||
@@ -141,6 +166,12 @@ const genArgs = (args: Fig.SingleOrArray<Fig.Arg> | undefined): string => {
|
||||
return args != null ? generateArgs(args) : "";
|
||||
};
|
||||
|
||||
const genSuggestions = (
|
||||
suggestions: (string | Fig.Suggestion)[] | undefined
|
||||
): string => {
|
||||
return suggestions != null ? generateSuggestions(suggestions) : "";
|
||||
};
|
||||
|
||||
const genSubcommands = (
|
||||
subcommand: Fig.SingleOrArray<Fig.Subcommand> | undefined
|
||||
): string => {
|
||||
@@ -151,14 +182,56 @@ const genSubcommands = (
|
||||
.join(",")}},`;
|
||||
};
|
||||
|
||||
const genIsPersistent = (persistent: boolean): string => {
|
||||
return persistent === true ? "IsPersistent: true," : "";
|
||||
};
|
||||
|
||||
const genExclusiveOn = (exclusiveOn: string[] | undefined): string => {
|
||||
if (exclusiveOn == null) return "";
|
||||
return `ExclusiveOn: []string{${exclusiveOn
|
||||
.map((e) => `"${e}"`)
|
||||
.join(",")}},`;
|
||||
};
|
||||
|
||||
const generateSuggestions = (
|
||||
suggestions: (string | Fig.Suggestion)[]
|
||||
): string => {
|
||||
const genName = (name: Fig.SingleOrArray<string>) => {
|
||||
return Array.isArray(name)
|
||||
? `Name: []string{${name
|
||||
.map((n) => `\`${escapeSpecialCharacters(n)}\``)
|
||||
.join(",")}},`
|
||||
: `Name: []string{\`${escapeSpecialCharacters(name)}\`},`;
|
||||
};
|
||||
|
||||
const generatedSuggestions = suggestions
|
||||
.map((suggestion) => {
|
||||
if (typeof suggestion == "string") {
|
||||
return `{Name: []string{\`${escapeSpecialCharacters(suggestion)}\`}}`;
|
||||
}
|
||||
if (suggestion.name == null) {
|
||||
return null;
|
||||
}
|
||||
return `{
|
||||
${genName(suggestion.name)}
|
||||
${genDescription(suggestion.description)}
|
||||
}`;
|
||||
})
|
||||
.filter((s) => s != null)
|
||||
.join(",");
|
||||
|
||||
return `Suggestions: []model.Suggestion{${generatedSuggestions}},`;
|
||||
};
|
||||
|
||||
const generateArgs = (args: Fig.SingleOrArray<Fig.Arg>): string => {
|
||||
const argList = Array.isArray(args) ? args : [args];
|
||||
const generatedArgs = argList
|
||||
.map((arg) => {
|
||||
return `{
|
||||
${genTemplates(arg.template)}
|
||||
${genName(arg.name)}
|
||||
${genSingleName(arg.name)}
|
||||
${genDescription(arg.description)}
|
||||
${genSuggestions(arg.suggestions)}
|
||||
}`;
|
||||
})
|
||||
.join(",");
|
||||
@@ -171,6 +244,8 @@ const generateOptions = (options: Fig.Option[]): string => {
|
||||
${genName(option.name)}
|
||||
${genDescription(option.description)}
|
||||
${genArgs(option.args)}
|
||||
${genIsPersistent(option.isPersistent)}
|
||||
${genExclusiveOn(option.exclusiveOn)}
|
||||
}`;
|
||||
});
|
||||
return `Options: []model.Option{${generatedOptions}},`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package model
|
||||
|
||||
type Subcommand struct {
|
||||
Name interface{} //single or array string, required
|
||||
Name []string //single or array string, required
|
||||
Description string
|
||||
Args []Arg
|
||||
Options []Option
|
||||
@@ -9,15 +9,26 @@ type Subcommand struct {
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
Name interface{} //single or array string, required
|
||||
Args interface{} //single or array Arg, optional
|
||||
Description string //single, optional
|
||||
Name []string //single or array string, required
|
||||
Args []Arg //single or array Arg, optional
|
||||
Description string //single, optional
|
||||
IsPersistent bool
|
||||
ExclusiveOn []string
|
||||
}
|
||||
|
||||
type Arg struct {
|
||||
Name string //single, optional
|
||||
Description string //single, optional
|
||||
Templates []Template
|
||||
Suggestions []Suggestion
|
||||
IsVariadic bool
|
||||
IsOptional bool
|
||||
IsCommand bool
|
||||
}
|
||||
|
||||
type Suggestion struct {
|
||||
Name []string
|
||||
Description string
|
||||
}
|
||||
|
||||
type Template string
|
||||
|
||||
@@ -13,6 +13,7 @@ var (
|
||||
type commandToken struct {
|
||||
token string
|
||||
complete bool
|
||||
isOption bool
|
||||
}
|
||||
|
||||
func ParseCommand(cmd string) []commandToken {
|
||||
@@ -48,7 +49,7 @@ func parse(cmd []rune) []commandToken {
|
||||
results = append(results, commandToken{complete: complete, token: string(cmd[readingIdx : idx+1])})
|
||||
} else if readingFlag && (unicode.IsSpace(r) || r == '=') {
|
||||
readingFlag = false
|
||||
results = append(results, commandToken{complete: true, token: string(cmd[readingIdx:idx])})
|
||||
results = append(results, commandToken{complete: true, token: string(cmd[readingIdx:idx]), isOption: true})
|
||||
} else if readingCmd && unicode.IsSpace(r) {
|
||||
readingCmd = false
|
||||
results = append(results, commandToken{complete: true, token: string(cmd[readingIdx:idx])})
|
||||
|
||||
Reference in New Issue
Block a user