fishi0x01/vsh

View on GitHub
completer/completer.go

Summary

Maintainability
A
0 mins
Test Coverage
package completer

import (
    "github.com/fishi0x01/vsh/log"
    "strings"

    "github.com/c-bata/go-prompt"
    "github.com/fatih/structs"
    "github.com/fishi0x01/vsh/cli"
    "github.com/fishi0x01/vsh/client"
)

// Completer struct for tab completion
type Completer struct {
    pathCompletionToggle bool
    client               *client.Client
}

// NewCompleter creates a new Completer with given client
func NewCompleter(client *client.Client, disableAutoCompletion bool) *Completer {
    return &Completer{
        pathCompletionToggle: !disableAutoCompletion,
        client:               client,
    }
}

// TogglePathCompletion enable/disable path auto-completion
func (c *Completer) TogglePathCompletion() {
    c.pathCompletionToggle = !c.pathCompletionToggle
    log.UserInfo("Use path auto-completion: %t", c.pathCompletionToggle)
}

func (c *Completer) getAbsoluteTopLevelSuggestions() []prompt.Suggest {
    var suggestions []prompt.Suggest
    for k := range c.client.KVBackends {
        suggestions = append(suggestions, prompt.Suggest{Text: "/" + k})
    }
    return suggestions
}

func (c *Completer) getRelativeTopLevelSuggestions() []prompt.Suggest {
    var suggestions []prompt.Suggest
    for k := range c.client.KVBackends {
        suggestions = append(suggestions, prompt.Suggest{Text: k})
    }
    return suggestions
}

func (c *Completer) absolutePathSuggestions(arg string) (result []prompt.Suggest) {
    if strings.Count(arg, "/") < 2 {
        result = c.getAbsoluteTopLevelSuggestions()
    } else {
        li := strings.LastIndex(arg, "/")
        queryPath := arg[0 : li+1]

        var options []string
        var err error
        options, err = c.client.List(queryPath)

        if err != nil {
            log.UserError("Error during auto-completion: %s", err)
            return result
        }

        options = append(options, "../")
        for _, node := range options {
            result = append(result, prompt.Suggest{Text: queryPath + node})
        }
    }

    filtered := prompt.FilterHasPrefix(result, arg, true)
    if len(filtered) > 0 {
        result = filtered
    }
    return result
}

func (c *Completer) relativePathSuggestions(arg string) (result []prompt.Suggest) {
    if c.client.Pwd == "/" && strings.Count(arg, "/") < 1 {
        result = c.getRelativeTopLevelSuggestions()
    } else {
        li := strings.LastIndex(arg, "/")
        queryPath := arg[0 : li+1]

        var options []string
        var err error
        options, err = c.client.List(c.client.Pwd + queryPath)

        if err != nil {
            return result
        }

        options = append(options, "../")
        for _, node := range options {
            result = append(result, prompt.Suggest{Text: queryPath + node})
        }
    }

    filtered := prompt.FilterHasPrefix(result, arg, true)
    if len(filtered) > 0 {
        result = filtered
    }
    return result
}

func isAbsolutePath(path string) bool {
    return strings.HasPrefix(path, "/")
}

func (c *Completer) isCommandArgument(p string) bool {
    words := strings.Split(p, " ")
    if len(words) < 2 {
        return false
    }

    commands := cli.NewCommands(c.client)
    for _, f := range structs.Fields(commands) {
        if words[0] == f.Value().(cli.Command).GetName() || words[0] == "toggle-auto-completion" {
            return true
        }
    }
    return false
}

func isCommand(p string) bool {
    return len(strings.Split(p, " ")) < 2
}

func (c *Completer) commandSuggestions(arg string) (result []prompt.Suggest) {
    result = make([]prompt.Suggest, 0)
    commands := cli.NewCommands(c.client)
    for _, f := range structs.Fields(commands) {
        val := f.Value().(cli.Command)
        result = append(result, prompt.Suggest{Text: val.GetName(), Description: cli.Usage(val)})
    }
    result = append(result, prompt.Suggest{Text: "toggle-auto-completion", Description: "toggle path auto-completion on/off"})

    filtered := prompt.FilterHasPrefix(result, arg, true)
    if len(filtered) > 0 {
        result = filtered
    }
    return result
}

// Complete suggestions for completion
func (c *Completer) Complete(in prompt.Document) (result []prompt.Suggest) {
    p := in.TextBeforeCursor()
    if isCommand(p) {
        result = c.commandSuggestions(in.GetWordBeforeCursor())
    } else if c.isCommandArgument(p) && c.pathCompletionToggle {
        cur := in.GetWordBeforeCursor()
        if isAbsolutePath(cur) {
            result = c.absolutePathSuggestions(cur)
        } else {
            result = c.relativePathSuggestions(cur)
        }
    }

    return result
}

// PromptPrefix returns the currently active prompt prefix
func (c *Completer) PromptPrefix() (string, bool) {
    return c.client.Name + " " + c.client.Pwd + "> ", true
}