diff --git a/ast/func_table.go b/ast/func_table.go index b70f758..3a2db29 100644 --- a/ast/func_table.go +++ b/ast/func_table.go @@ -75,3 +75,18 @@ func GetFunction(arg string, table FuncTable) *Function { return target } + + +/* lists all functions in table + */ +func ListFuncs(ft FuncTable) []string { + keys := make([]string, len(*ft)) + i := 0 + + for k := range *ft { + keys[i] = k + i++ + } + + return keys +} diff --git a/ast/var_table.go b/ast/var_table.go index 7037e4a..072ec99 100644 --- a/ast/var_table.go +++ b/ast/var_table.go @@ -73,6 +73,18 @@ func SetVar(variable string, value *Token, vt VarTable) { } } +/* lists all vars in tables + */ +func ListVars(vt VarTable) []string { + keys := make([]string, len(*vt)) + i := 0 + for k := range *vt { + keys[i] = k + } + + return keys +} + // Library represents variables defined in inner scope // It is assumed library is ordered from innermost scope to outermost scope func GetVarFromTables(arg string, library []VarTable) *Token { diff --git a/cmd/shs.go b/cmd/shs.go index 4b43d3c..3413d21 100644 --- a/cmd/shs.go +++ b/cmd/shs.go @@ -20,10 +20,12 @@ package main import ( "os" "fmt" + "strings" "strconv" "github.com/peterh/liner" "gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/log" + "gitlab.com/whom/shs/util" "gitlab.com/whom/shs/config" ) @@ -105,6 +107,10 @@ func main() { defer line.Close() line.SetCtrlCAborts(true) + line.SetCompleter(func(line string) (c []string) { + strtoks := strings.Split(line, " ") + return util.ShellCompleter(strtoks[len(strtoks) - 1], vars, funcs) + }) var histFile *os.File var err error @@ -134,12 +140,10 @@ func main() { text, err := line.Prompt(prompt) if err != nil && err != liner.ErrPromptAborted{ log.Log(log.ERR, "couldnt read user input: " + err.Error(), "repl") + continue } - if !no_hist { - line.WriteHistory(histFile) - } - + line.AppendHistory(text) userInput := ast.Lex(text) if userInput == nil { // errors handled in Lex diff --git a/util/shell_complete.go b/util/shell_complete.go new file mode 100644 index 0000000..8246ff5 --- /dev/null +++ b/util/shell_complete.go @@ -0,0 +1,90 @@ +/* SHS: Syntactically Homogeneous Shell + * Copyright (C) 2019 Aidan Hahn + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package util + +import ( + "fmt" + "io/ioutil" + "strings" + "gitlab.com/whom/shs/log" + "gitlab.com/whom/shs/ast" +) + +// wrap this in a lambda that passes in the vt and ft +// I suppose it could be more optimal. Fix if it bothers you +func ShellCompleter(line string, vt ast.VarTable, ft ast.FuncTable) []string { + dir, path, tok := getPathBase(line) + compSource := []string{} + + if !path { + dir = "." + } else { + line = tok + } + + fobjs, err := ioutil.ReadDir(dir) + if err != nil { + log.Log(log.DEBUG, + "couldnt read dir " + dir + ": " + err.Error(), + "complete") + if path { + return nil + } + } else { + for _, f := range fobjs { + compSource = append(compSource, f.Name()) + } + } + + if !path { + compSource = append(compSource, ast.ListVars(vt)...) + compSource = append(compSource, ast.ListFuncs(ft)...) + } + + completions := []string{} + for _, i := range compSource { + if strings.HasPrefix(i, line) { + completions = append(completions, i) + } + } + + return completions +} + +// returns everything up to the last '/' +// as well as whether or not a / was found +// and finally the token after the last / +func getPathBase(in string) (string, bool, string) { + if in == "" { + return "", false, "" + } + + isPath := false + + i := len(in) - 1 + for i > 0 { + if in[i] == '/' { + isPath = true + break + } + + i -= 1 + } + + return in[:i], isPath, in[i+1:] +}