diff --git a/Readme.md b/Readme.md
index 63d0b30..448e0b5 100644
--- a/Readme.md
+++ b/Readme.md
@@ -2,7 +2,7 @@
Syntactically Homogeneous Shell
## Overview
-This shell was created to have extremely simple syntax. S-Expressions were chosen to represent statements and the scope of language features were constrained to what could be considered practical for daily shell use. This program is meant to be practical for administrators and daily power users.
+This shell was created to have extremely simple syntax. S-Expressions were chosen to represent statements and the scope of language features were constrained to what could be considered practical for daily shell use. This program is meant to be practical for administrators and daily power users. It can be used for both system administration (as one might use bash or zsh) as well as for the creation of simple programs.
## Basic Syntax
When in doubt the `print_ast` utility can be used to examine the output of the Lex+Parse process. Here you can spot any bugs regarding syntax.
@@ -94,9 +94,16 @@ Here is an example of a shs configuration file:
(func _SH_PROMPT () (concat (?) ($ basename ($ pwd)) "\n"))
```
+## The Docs
+- [Documentation on language interpreter](https://godoc.org/gitlab.com/whom/shs/ast)
+- [Documentation on logging module](https://godoc.org/gitlab.com/whom/shs/log)
+- [Documentation on configuration module](https://godoc.org/gitlab.com/whom/shs/config)
+- [Documentation on utility functions](https://godoc.org/gitlab.com/whom/shs/util)
+- [Documentation on standard library](https://godoc.org/gitlab.com/whom/shs/stdlib)
+- [Guide to standard library](https://gitlab.com/whom/shs/-/blob/master/stdlib/Readme.md)
+
## Contributing
- Any contribution to this software is welcome as long as it adheres to the conduct guidelines specified in the `Contributing.md` file in this repository.
-- Consider reading the [STDLIB Readme](https://git.callpipe.com/aidan/shs/-/blob/master/stdlib/Readme.md) for more information on how to extend this project.
## License
Copyright (C) 2019 Aidan Hahn.
diff --git a/ast/func_table.go b/ast/func_table.go
index b939e0c..734f9ee 100644
--- a/ast/func_table.go
+++ b/ast/func_table.go
@@ -17,7 +17,10 @@
package ast
-import "gitlab.com/whom/shs/log"
+import (
+ "fmt"
+ "gitlab.com/whom/shs/log"
+)
/* expected function header for any stdlib function
*/
@@ -61,8 +64,12 @@ func (f Function) ParseFunction(args *Token) bool {
if i != 0 {
log.Log(log.ERR,
- "Incorrect number of arguments",
- "eval")
+ "Incorrect number of arguments",
+ "eval")
+ log.Log(log.DEBUG,
+ fmt.Sprintf("Function %s expects %d arguments. You've provided %d arguments.",
+ f.Name, f.Args, f.Args - i),
+ "eval")
return false
}
@@ -89,7 +96,7 @@ func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token {
func GetFunction(arg string, table FuncTable) *Function {
target, ok := (*table)[arg]
if !ok {
- log.Log(log.DEBUG,
+ log.Log(log.INFO,
"function " + arg + " not found",
"ftable")
return nil
diff --git a/cmd/shs.go b/cmd/shs.go
index 3379c7c..82fbf8d 100644
--- a/cmd/shs.go
+++ b/cmd/shs.go
@@ -21,10 +21,11 @@ import (
"os"
"fmt"
"strconv"
- "github.com/peterh/liner"
+ "github.com/candid82/liner"
"gitlab.com/whom/shs/ast"
"gitlab.com/whom/shs/log"
"gitlab.com/whom/shs/util"
+ "gitlab.com/whom/shs/stdlib"
"gitlab.com/whom/shs/config"
)
@@ -77,6 +78,9 @@ func main() {
ast.SyncTablesWithOSEnviron = true
ast.ExecWhenFuncUndef = true
+ stdlib.InitShellFeatures()
+ defer stdlib.TeardownShell()
+
vars, funcs := config.InitFromConfig(".shsrc")
debug_t := ast.GetVar("SH_DEBUG_MODE", vars)
if debug_t != nil {
@@ -105,6 +109,12 @@ func main() {
line := liner.NewLiner()
defer line.Close()
+ if !liner.TerminalSupported() {
+ log.Log(log.ERR,
+ "Terminal unsupported, continuing in dummy mode!",
+ "init")
+ }
+
line.SetCtrlCAborts(true)
line.SetCompleter(func(line string) (c []string) {
return util.ShellCompleter(line, vars, funcs)
@@ -126,6 +136,8 @@ func main() {
for {
setLogLvl(vars)
+ stdlib.CheckBGProcs()
+
var prePrompt string
if dyn_prompt != nil {
p_tok := dyn_prompt.CallFunction(nil, vars, funcs)
diff --git a/cmd/test_shell.go b/cmd/test_shell.go
new file mode 100644
index 0000000..f783dc7
--- /dev/null
+++ b/cmd/test_shell.go
@@ -0,0 +1,36 @@
+/* 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 main
+
+import (
+ "fmt"
+ "time"
+ "gitlab.com/whom/shs/log"
+ "gitlab.com/whom/shs/stdlib"
+)
+
+func main() {
+ log.SetLogLvl(3)
+ stdlib.InitShellFeatures()
+ stdlib.LaunchProcess("/usr/bin/vim", []string{"test.txt"}, false)
+
+ fmt.Println("process started, now sleeping for 10 sec")
+ time.Sleep(10 * time.Second)
+
+ stdlib.TeardownShell()
+}
diff --git a/config/config.go b/config/config.go
index fc8eda3..feef48e 100644
--- a/config/config.go
+++ b/config/config.go
@@ -39,7 +39,7 @@ func InitFromConfig(configFile string) (ast.VarTable, ast.FuncTable) {
util.LoadScript(configFile, vars, funcs)
- log.Log(log.DEBUG,
+ log.Log(log.INFO,
"config file fully evaluated",
"config")
return vars, funcs
diff --git a/go.mod b/go.mod
index 2b85407..7b6cc51 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,8 @@ module gitlab.com/whom/shs
go 1.14
require (
+ github.com/candid82/liner v1.4.0 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/peterh/liner v1.2.0 // indirect
+ golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666
)
diff --git a/go.sum b/go.sum
index 285b298..dbd0289 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,10 @@
+github.com/candid82/liner v1.4.0 h1:nUhs4pv/cnpnBERwJHmqmgargZTWnPbDJ67HtQcfSTo=
+github.com/candid82/liner v1.4.0/go.mod h1:shD5EWTOYasmaGjMfuaB82N9YxGMIAEoXjQEH6RoGvo=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/peterh/liner v1.2.0 h1:w/UPXyl5GfahFxcTOz2j9wCIHNI+pUPr2laqpojKNCg=
github.com/peterh/liner v1.2.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
+golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 h1:gVCS+QOncANNPlmlO1AhlU3oxs4V9z+gTtPwIk3p2N8=
+golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/log/logger.go b/log/logger.go
index 2d736e6..19732e9 100644
--- a/log/logger.go
+++ b/log/logger.go
@@ -51,5 +51,5 @@ func Log(lvl int, msg, context string) {
return
}
- fmt.Println("[" + context + "] " + msg)
+ fmt.Println("[" + context + "]\033[3m " + msg + "\033[0m ")
}
diff --git a/resources/icon/icon.kra b/resources/icon/icon.kra
new file mode 100644
index 0000000..3b77b37
Binary files /dev/null and b/resources/icon/icon.kra differ
diff --git a/stdlib/control_flow.go b/stdlib/control_flow.go
index 0238ff5..51c4deb 100644
--- a/stdlib/control_flow.go
+++ b/stdlib/control_flow.go
@@ -45,8 +45,8 @@ func ShsProgn(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
* (if (eq (number "3") 3) (print "test passed") (print "test failed"))
*/
func ShsIf(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
- cond := in
- t := cond.Next
+ cond := in.Copy()
+ t := cond.Next.Copy()
f := t.Next
cond.Next = nil
t.Next = nil
diff --git a/stdlib/list.go b/stdlib/list.go
index cec7d80..748414b 100644
--- a/stdlib/list.go
+++ b/stdlib/list.go
@@ -29,7 +29,7 @@ import (
*/
func Expand(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token {
if input.Tag != ast.LIST {
- log.Log(log.DEBUG, "expand called on not a list", "expand")
+ log.Log(log.INFO, "expand called on not a list", "expand")
return input
}
diff --git a/stdlib/call.go b/stdlib/shell.go
similarity index 50%
rename from stdlib/call.go
rename to stdlib/shell.go
index 99e0647..e67a4bd 100644
--- a/stdlib/call.go
+++ b/stdlib/shell.go
@@ -19,30 +19,205 @@ package stdlib
import (
"os"
+ "io"
"fmt"
"bytes"
- "strconv"
"os/exec"
+ "context"
"syscall"
+ "strconv"
"os/signal"
+ "golang.org/x/sys/unix"
"gitlab.com/whom/shs/ast"
"gitlab.com/whom/shs/log"
)
-var bgProcs = make([]*exec.Cmd, 0)
-var sigs = []os.Signal{
- os.Interrupt,
- syscall.SIGTERM,
- syscall.SIGTSTP,
- syscall.SIGTTIN,
- syscall.SIGTTOU,
- syscall.SIGCONT,
+var sigChan chan os.Signal
+var waitChan chan error
+
+/* mapping of pid to Proc
+ */
+var JobMap = map[int]Proc{}
+
+/* id of shell process group
+ */
+var pgid, pid int
+
+/* cancel func for current running process
+ */
+var CurCancel *context.CancelFunc
+
+
+/* Holds an os/exec Cmd object and its context cancel function
+ */
+type Proc struct {
+ Ctl *exec.Cmd
+ Cancel context.CancelFunc
+}
+
+var catchMe = []os.Signal{
+ syscall.SIGINT,
}
/* Exit code of last run process
*/
var LastExitCode int
+// Job control handlers
+func signalHandler() {
+ for {
+ <- sigChan
+ log.Log(log.DEBUG,
+ "caught SIGINT",
+ "jobctl")
+
+ if CurCancel != nil {
+ (*CurCancel)()
+ }
+ }
+}
+
+// for some reason not implemented in stdlib
+func tcsetpgrp(fd int, pgrp int) error {
+ return unix.IoctlSetPointerInt(fd, unix.TIOCSPGRP, pgrp)
+}
+
+// wrapper for convenience
+func setSigState() {
+ signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN, syscall.SIGTSTP)
+ signal.Notify(sigChan, syscall.SIGINT, syscall.SIGQUIT)
+}
+
+/* Sets pgid
+ * Installs a sigchld handler
+ * returns true if success, false on error
+ */
+func InitShellFeatures() bool {
+ // TODO: adjust capacity, make configurable maybe?
+ sigChan = make(chan os.Signal, 5)
+ waitChan = make(chan error, 5)
+
+ go signalHandler()
+
+ setSigState()
+ pid = os.Getpid()
+ var errr error
+ pgid, errr = syscall.Getpgid(pid)
+ if errr != nil {
+ log.Log(log.ERR,
+ "Failure to get pgid: " + errr.Error(),
+ "jobctl")
+ return false
+ }
+
+ termPgrp, err := unix.IoctlGetInt(0, unix.TIOCGPGRP)
+ if err != nil {
+ log.Log(log.ERR,
+ "Failure to get pgrp: " + err.Error(),
+ "jobctl")
+ return false
+ }
+
+ if pgid != termPgrp {
+ syscall.Kill(-termPgrp, unix.SIGTTIN)
+ }
+
+ syscall.Setpgid(0, 0)
+ tcsetpgrp(0, pid)
+
+ return true
+}
+
+/* Tears down session
+ */
+func TeardownShell() {
+ close(sigChan)
+ close(waitChan)
+ for k := range JobMap {
+ JobMap[k].Cancel()
+ }
+}
+
+/* Check background processes states
+ * If any have finished, reap them
+ */
+func CheckBGProcs() {
+ for key, proc := range JobMap {
+ maybeFilePath := fmt.Sprintf("/proc/%d", key)
+ _, err := os.Stat(maybeFilePath)
+
+ if os.IsNotExist(err) {
+ if err := proc.Ctl.Process.Release(); err != nil {
+ log.Log(log.ERR,
+ fmt.Sprintf("Failed to release exited process %d: %s",
+ key, err.Error()),
+ "jobctl")
+ } else {
+ // not actually an error, just abusing default visibility
+ log.Log(log.ERR,
+ fmt.Sprintf("Child [%d] exited", key),
+ "jobctl")
+ delete(JobMap, key)
+ }
+ } else if err != nil {
+ log.Log(log.DEBUG,
+ "Error checking if process exists in proc: " + err.Error(),
+ "jobctl")
+ }
+ }
+}
+
+/* Makes and stores a new process in the job control
+ * uses os/exec Cmd object. sets SysProcAttr.Foreground
+ * calls Cmd.Start()
+ */
+func LaunchProcess(
+ path string,
+ args []string,
+ background bool,
+ stdin io.Reader,
+ stdout io.Writer,
+ stderr io.Writer ) {
+ c, cancel := context.WithCancel(context.Background())
+ cmd := exec.CommandContext(c, path, args...)
+ cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+
+ if !background {
+ cmd.SysProcAttr.Foreground = true
+ }
+
+ if stdin == nil {
+ stdin = os.Stdin
+ }
+
+ if stdout == nil {
+ stdout = os.Stdout
+ }
+
+ if stderr == nil {
+ stderr = os.Stderr
+ }
+
+ cmd.Stdin = stdin
+ cmd.Stdout = stdout
+ cmd.Stderr = stderr
+
+ cmd.Start()
+ cpid := cmd.Process.Pid
+
+ // TODO: check for clobber?
+ // unlikely, but PIDs do reset after a while
+ JobMap[cpid] = Proc{ Ctl: cmd, Cancel: cancel }
+ CurCancel = &cancel
+
+ if !background {
+ cmd.Wait()
+ tcsetpgrp(0, pgid)
+ delete(JobMap, cpid)
+ CurCancel = nil
+ }
+}
+
/* Takes n arguments (list of tokens generated by lexing a shell command)
* Evaluates arguments, but does not err on undefined symbols (note the last arg to Eval(...))
* Executes shell command and returns nil
@@ -76,35 +251,7 @@ func Call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
args = append(args, i.Value())
}
- var cmd *exec.Cmd
- if len(args) > 0 {
- cmd = exec.Command(path, args...)
- } else {
- cmd = exec.Command(path)
- }
- cmd.Env = os.Environ()
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
-
- signalChan := make(chan os.Signal, 2)
- signal.Notify(signalChan, sigs...)
- go func() {
- sig := <-signalChan
- cmd.Process.Signal(sig)
- }()
-
- err = cmd.Run()
- close(signalChan)
- signal.Reset(sigs...)
- if err != nil {
- if exitError, ok := err.(*exec.ExitError); ok {
- LastExitCode = exitError.ExitCode()
- } else {
- log.Log(log.ERR, "Execution step returned unparsable error: " + err.Error(), "call")
- }
- }
-
+ LaunchProcess(path, args, false, nil, nil, nil)
return nil
}
@@ -114,6 +261,7 @@ func Call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
* Example: (bg vim file.txt)
*/
func Bgcall(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
+ in = in.Eval(ft, vt, true)
if in == nil {
return nil
}
@@ -139,64 +287,61 @@ func Bgcall(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
args = append(args, i.Value())
}
- var cmd *exec.Cmd
- if len(args) > 0 {
- cmd = exec.Command(path, args...)
- } else {
- cmd = exec.Command(path)
- }
- cmd.Stderr = os.Stderr
-
- bgProcs = append(bgProcs, cmd)
- cmd.Stdout = os.Stdout
- cmd.Stdin = os.Stdin
- cmd.Stderr = os.Stderr
-
- cmd.Start()
- cmd.Process.Signal(syscall.SIGTSTP)
+ LaunchProcess(path, args, true, nil, nil, nil)
return nil
}
-/* brings last BG'ed process into the foreground
+/* takes one argument: pid of process to foreground
* returns nil
*
* Example:
* (bg vim file.txt)
- * (fg)
+ * ( )
+ * (fg )
*/
func Fg(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
- if len(bgProcs) < 1 {
+ if len(JobMap) < 1 {
return nil
}
- cmd := bgProcs[0]
- bgProcs = bgProcs[1:]
-
- signalChan := make(chan os.Signal, 2)
- signal.Notify(signalChan, sigs...)
- go func() {
- sig := <-signalChan
- cmd.Process.Signal(sig)
- }()
-
- cmd.Process.Signal(syscall.SIGCONT)
- err := cmd.Wait()
- close(signalChan)
- signal.Reset(sigs...)
- if err != nil {
- if exitError, ok := err.(*exec.ExitError); ok {
- LastExitCode = exitError.ExitCode()
- } else {
- log.Log(log.ERR, "Execution step returned error: " + err.Error(), "call")
- }
+ in = in.Eval(ft, vt, false)
+ if in.Tag != ast.NUMBER && in.Tag != ast.STRING {
+ log.Log(log.ERR,
+ "must supply a number or string to fg",
+ "fg")
+ return nil
}
+ pid, err := strconv.ParseFloat(in.Value(), 64)
+ if err != nil {
+ log.Log(log.ERR,
+ "value supplied to fg could not be cast to float",
+ "fg")
+ return nil
+ }
+
+ ipid := int(pid)
+ proc, ok := JobMap[ipid]
+ if !ok {
+ log.Log(log.ERR,
+ "Process not found, was it started by this shell?",
+ "fg")
+ return nil
+ }
+
+ if err := tcsetpgrp(0, ipid); err != nil {
+ log.Log(log.ERR,
+ "Error foregrounding process: " + err.Error(),
+ "fg")
+ return nil
+ }
+
+ proc.Ctl.Wait()
return nil
}
/* Takes 0 args
- * returns a string containing info about current jobs
- * returns total jobs as well as their PIDs and place in the bg queue
+ * returns a list of PIDs
*
* Example:
* (bg ping google.com)
@@ -208,22 +353,18 @@ func Jobs(in *ast.Token, vt ast.VarTable, fg ast.FuncTable) *ast.Token {
Tag: ast.LIST,
}
- _inner := &ast.Token{
- Tag: ast.STRING,
- }
-
- ret.Direct(_inner)
- _inner.Set(fmt.Sprintf("Total: %d", len(bgProcs)))
-
+ var _inner *ast.Token
iter := &_inner
- for i := 0; i < len(bgProcs); i += 1 {
- (*iter).Next = &ast.Token{
+ for k := range JobMap {
+ (*iter) = &ast.Token{
Tag: ast.STRING,
}
- (*iter).Next.Set(fmt.Sprintf("[%d]: %d", i, bgProcs[i].Process.Pid))
+
+ (*iter).Set(fmt.Sprintf("%d", k))
iter = &(*iter).Next
}
+ ret.Direct(_inner)
return ret
}
@@ -244,7 +385,7 @@ func ReadCmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
return nil
}
- var out bytes.Buffer
+ out := new(bytes.Buffer)
path, err := exec.LookPath(in.Value())
if err != nil {
log.Log(log.ERR, "Couldnt exec " + in.Value() + ", file not found", "call")
@@ -261,34 +402,7 @@ func ReadCmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
args = append(args, i.Value())
}
- var cmd *exec.Cmd
- if len(args) > 0 {
- cmd = exec.Command(path, args...)
- } else {
- cmd = exec.Command(path)
- }
- cmd.Stdout = &out
- cmd.Stderr = os.Stderr
- cmd.Stdin = os.Stdin
-
- signalChan := make(chan os.Signal, 2)
- signal.Notify(signalChan, sigs...)
- go func() {
- sig := <-signalChan
- cmd.Process.Signal(sig)
- }()
-
- err = cmd.Run()
- close(signalChan)
- signal.Reset(sigs...)
- if err != nil {
- if exitError, ok := err.(*exec.ExitError); ok {
- LastExitCode = exitError.ExitCode()
- } else {
- log.Log(log.ERR, "Execution step returned error: " + err.Error(), "$")
- }
- }
-
+ LaunchProcess(path, args, false, nil, out, nil)
output := out.String()
olen := len(output)
if olen > 0 && output[olen - 1] == '\n' {
@@ -333,33 +447,16 @@ func Kill(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
return nil
}
- found := false
- newBgProcs := []*exec.Cmd{}
- for _, i := range bgProcs {
- if i.Process.Pid != int(pid) {
- newBgProcs = append(newBgProcs, i)
- } else {
- found = true
- err = i.Process.Kill()
- if err != nil {
- log.Log(log.ERR, fmt.Sprintf("error killing process %d: %s",
- int(pid), err.Error()), "kill")
- newBgProcs = append(newBgProcs, i)
- }
- }
- }
-
- bgProcs = newBgProcs
-
- if !found {
- // docs say no error on unix systems
- proc, _ := os.FindProcess(int(pid))
- err = proc.Kill()
- if err != nil {
- log.Log(log.ERR, fmt.Sprintf("error killing process %d: %s",
- int(pid), err.Error()), "kill")
- }
+ proc, ok := JobMap[int(pid)]
+ if !ok {
+ log.Log(log.ERR,
+ "Couldnt find process " + in.Value() + ", was it started by this shell?",
+ "kill")
+ return nil
}
+ // if this doesnt work do proc.Ctl.Kill()
+ proc.Cancel()
+ delete(JobMap, int(pid))
return nil
}
diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go
index 83749c2..292b7da 100644
--- a/stdlib/stdlib.go
+++ b/stdlib/stdlib.go
@@ -238,7 +238,7 @@ func GenFuncTable() ast.FuncTable {
Function: Fg,
Name: "foreground",
TimesCalled: 0,
- Args: 0,
+ Args: 1,
},
"$": &ast.Function{
diff --git a/util/shell_complete.go b/util/shell_complete.go
index a6e3289..86410d8 100644
--- a/util/shell_complete.go
+++ b/util/shell_complete.go
@@ -51,7 +51,7 @@ func ShellCompleter(line string, vt ast.VarTable, ft ast.FuncTable) []string {
fobjs, err := ioutil.ReadDir(dir)
if err != nil {
- log.Log(log.DEBUG,
+ log.Log(log.INFO,
"couldnt read dir " + dir + ": " + err.Error(),
"complete")
if path {
@@ -59,7 +59,16 @@ func ShellCompleter(line string, vt ast.VarTable, ft ast.FuncTable) []string {
}
} else {
for _, f := range fobjs {
- compSource = append(compSource, dir + "/" + f.Name())
+ fileTok := f.Name()
+ if dir != "." {
+ fileTok = dir + "/" + fileTok
+ }
+
+ if f.IsDir() {
+ fileTok = fileTok + "/"
+ }
+
+ compSource = append(compSource, fileTok)
}
}