2021-05-16 19:53:21 +00:00
|
|
|
// Here is the core api for the hilbi shell itself
|
|
|
|
// Basically, stuff about the shell itself and other functions
|
|
|
|
// go here.
|
|
|
|
package main
|
|
|
|
|
2021-05-16 21:13:28 +00:00
|
|
|
import (
|
2022-04-04 10:40:02 +00:00
|
|
|
"bytes"
|
|
|
|
"errors"
|
2022-01-26 19:51:52 +00:00
|
|
|
"fmt"
|
2021-05-16 21:13:28 +00:00
|
|
|
"os"
|
2022-01-26 19:51:52 +00:00
|
|
|
"os/exec"
|
2021-09-26 01:39:06 +00:00
|
|
|
"runtime"
|
2021-07-08 10:44:57 +00:00
|
|
|
"strings"
|
2022-01-26 19:51:52 +00:00
|
|
|
"syscall"
|
|
|
|
"time"
|
2021-05-16 21:13:28 +00:00
|
|
|
|
2021-10-16 19:40:08 +00:00
|
|
|
"hilbish/util"
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
rt "github.com/arnodel/golua/runtime"
|
|
|
|
"github.com/arnodel/golua/lib/packagelib"
|
2022-03-02 02:00:46 +00:00
|
|
|
"github.com/maxlandon/readline"
|
2022-03-02 23:11:57 +00:00
|
|
|
"github.com/blackfireio/osinfo"
|
2021-05-16 21:13:28 +00:00
|
|
|
"mvdan.cc/sh/v3/interp"
|
|
|
|
)
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
var exports = map[string]util.LuaExport{
|
|
|
|
"alias": {hlalias, 2, false},
|
|
|
|
"appendPath": {hlappendPath, 1, false},
|
|
|
|
"complete": {hlcomplete, 2, false},
|
|
|
|
"cwd": {hlcwd, 0, false},
|
|
|
|
"exec": {hlexec, 1, false},
|
|
|
|
"runnerMode": {hlrunnerMode, 1, false},
|
|
|
|
"goro": {hlgoro, 1, true},
|
|
|
|
"highlighter": {hlhighlighter, 1, false},
|
|
|
|
"hinter": {hlhinter, 1, false},
|
|
|
|
"multiprompt": {hlmultiprompt, 1, false},
|
|
|
|
"prependPath": {hlprependPath, 1, false},
|
|
|
|
"prompt": {hlprompt, 1, false},
|
|
|
|
"inputMode": {hlinputMode, 1, false},
|
|
|
|
"interval": {hlinterval, 2, false},
|
|
|
|
"read": {hlread, 1, false},
|
|
|
|
"run": {hlrun, 1, true},
|
|
|
|
"timeout": {hltimeout, 2, false},
|
|
|
|
"which": {hlwhich, 1, false},
|
2021-05-16 21:13:28 +00:00
|
|
|
}
|
|
|
|
|
2021-12-31 17:25:53 +00:00
|
|
|
var greeting string
|
2022-04-04 10:40:02 +00:00
|
|
|
var hshMod *rt.Table
|
|
|
|
var hilbishLoader = packagelib.Loader{
|
|
|
|
Load: hilbishLoad,
|
|
|
|
Name: "hilbish",
|
|
|
|
}
|
2021-12-31 17:25:53 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
|
|
|
mod := rt.NewTable()
|
|
|
|
util.SetExports(rtm, mod, exports)
|
2022-03-02 02:00:46 +00:00
|
|
|
hshMod = mod
|
2021-05-16 21:13:28 +00:00
|
|
|
|
|
|
|
host, _ := os.Hostname()
|
2021-09-26 01:39:06 +00:00
|
|
|
username := curuser.Username
|
2021-12-31 17:25:53 +00:00
|
|
|
|
2021-09-26 01:39:06 +00:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
username = strings.Split(username, "\\")[1] // for some reason Username includes the hostname on windows
|
|
|
|
}
|
2021-05-16 21:13:28 +00:00
|
|
|
|
2022-03-16 22:42:38 +00:00
|
|
|
greeting = `Welcome to {magenta}Hilbish{reset}, {cyan}` + username + `{reset}.
|
|
|
|
The nice lil shell for {blue}Lua{reset} fanatics!
|
|
|
|
Check out the {blue}{bold}guide{reset} command to get started.
|
|
|
|
`
|
2022-04-04 10:40:02 +00:00
|
|
|
util.SetField(rtm, mod, "ver", rt.StringValue(version), "Hilbish version")
|
|
|
|
util.SetField(rtm, mod, "user", rt.StringValue(username), "Username of user")
|
|
|
|
util.SetField(rtm, mod, "host", rt.StringValue(host), "Host name of the machine")
|
|
|
|
util.SetField(rtm, mod, "home", rt.StringValue(curuser.HomeDir), "Home directory of the user")
|
|
|
|
util.SetField(rtm, mod, "dataDir", rt.StringValue(dataDir), "Directory for Hilbish's data files")
|
|
|
|
util.SetField(rtm, mod, "interactive", rt.BoolValue(interactive), "If this is an interactive shell")
|
|
|
|
util.SetField(rtm, mod, "login", rt.BoolValue(login), "Whether this is a login shell")
|
|
|
|
util.SetField(rtm, mod, "greeting", rt.StringValue(greeting), "Hilbish's welcome message for interactive shells. It has Lunacolors formatting.")
|
|
|
|
util.SetField(rtm, mod, "vimMode", rt.NilValue, "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
|
|
|
util.SetField(rtm, hshMod, "exitCode", rt.IntValue(0), "Exit code of last exected command")
|
|
|
|
util.Document(mod, "Hilbish's core API, containing submodules and functions which relate to the shell itself.")
|
2021-05-16 21:13:28 +00:00
|
|
|
|
2021-12-15 00:54:23 +00:00
|
|
|
// hilbish.userDir table
|
2022-04-04 10:40:02 +00:00
|
|
|
hshuser := rt.NewTable()
|
2021-12-14 00:13:17 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
util.SetField(rtm, hshuser, "config", rt.StringValue(confDir), "User's config directory")
|
|
|
|
util.SetField(rtm, hshuser, "data", rt.StringValue(userDataDir), "XDG data directory")
|
|
|
|
util.Document(hshuser, "User directories to store configs and/or modules.")
|
|
|
|
mod.Set(rt.StringValue("userDir"), rt.TableValue(hshuser))
|
2022-03-05 19:59:00 +00:00
|
|
|
|
|
|
|
// hilbish.os table
|
2022-04-04 10:40:02 +00:00
|
|
|
hshos := rt.NewTable()
|
2022-03-02 23:11:57 +00:00
|
|
|
info, _ := osinfo.GetOSInfo()
|
2022-03-05 19:59:00 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
util.SetField(rtm, hshos, "family", rt.StringValue(info.Family), "Family name of the current OS")
|
|
|
|
util.SetField(rtm, hshos, "name", rt.StringValue(info.Name), "Pretty name of the current OS")
|
|
|
|
util.SetField(rtm, hshos, "version", rt.StringValue(info.Version), "Version of the current OS")
|
|
|
|
util.Document(hshos, "OS info interface")
|
|
|
|
mod.Set(rt.StringValue("os"), rt.TableValue(hshos))
|
2021-10-08 00:58:07 +00:00
|
|
|
|
2021-12-15 00:54:23 +00:00
|
|
|
// hilbish.aliases table
|
2022-03-19 16:44:26 +00:00
|
|
|
aliases = newAliases()
|
2022-04-04 10:40:02 +00:00
|
|
|
aliasesModule := aliases.Loader(rtm)
|
|
|
|
util.Document(aliasesModule, "Alias inferface for Hilbish.")
|
|
|
|
mod.Set(rt.StringValue("aliases"), rt.TableValue(aliasesModule))
|
2021-12-15 00:54:23 +00:00
|
|
|
|
2022-01-27 21:02:21 +00:00
|
|
|
// hilbish.history table
|
2022-04-04 10:40:02 +00:00
|
|
|
historyModule := lr.Loader(rtm)
|
|
|
|
mod.Set(rt.StringValue("history"), rt.TableValue(historyModule))
|
|
|
|
util.Document(historyModule, "History interface for Hilbish.")
|
2022-01-27 21:02:21 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
// hilbish.completion table
|
|
|
|
hshcomp := rt.NewTable()
|
|
|
|
util.SetField(rtm, hshcomp, "files",
|
|
|
|
rt.FunctionValue(rt.NewGoFunction(luaFileComplete, "files", 3, false)),
|
|
|
|
"Completer for files")
|
2022-03-05 19:59:00 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
util.SetField(rtm, hshcomp, "bins",
|
|
|
|
rt.FunctionValue(rt.NewGoFunction(luaBinaryComplete, "bins", 3, false)),
|
|
|
|
"Completer for executables/binaries")
|
|
|
|
|
|
|
|
util.Document(hshcomp, "Completions interface for Hilbish.")
|
|
|
|
mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp))
|
2022-03-05 19:59:00 +00:00
|
|
|
|
2022-03-20 23:10:12 +00:00
|
|
|
// hilbish.runner table
|
2022-04-04 10:40:02 +00:00
|
|
|
runnerModule := runnerModeLoader(rtm)
|
|
|
|
util.Document(runnerModule, "Runner/exec interface for Hilbish.")
|
|
|
|
mod.Set(rt.StringValue("runner"), rt.TableValue(runnerModule))
|
2022-03-20 19:15:44 +00:00
|
|
|
|
2022-03-20 23:10:12 +00:00
|
|
|
// hilbish.jobs table
|
2022-03-19 17:10:50 +00:00
|
|
|
jobs = newJobHandler()
|
2022-04-04 10:40:02 +00:00
|
|
|
jobModule := jobs.loader(rtm)
|
|
|
|
util.Document(jobModule, "(Background) job interface.")
|
|
|
|
mod.Set(rt.StringValue("jobs"), rt.TableValue(jobModule))
|
2022-04-12 23:28:25 +00:00
|
|
|
|
|
|
|
timers = newTimerHandler()
|
|
|
|
timerModule := timers.loader(rtm)
|
|
|
|
util.Document(timerModule, "Timer interface, for control of all intervals and timeouts.")
|
|
|
|
mod.Set(rt.StringValue("timers"), rt.TableValue(timerModule))
|
2021-05-16 21:13:28 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return rt.TableValue(mod), nil
|
2021-05-16 21:13:28 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func getenv(key, fallback string) string {
|
|
|
|
value := os.Getenv(key)
|
|
|
|
if len(value) == 0 {
|
|
|
|
return fallback
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
}
|
2022-03-05 19:59:00 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
query, ctx, fds, err := getCompleteParams(t, c)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-03-05 19:59:00 +00:00
|
|
|
|
|
|
|
completions := fileComplete(query, ctx, fds)
|
2022-04-04 10:40:02 +00:00
|
|
|
luaComps := rt.NewTable()
|
2022-03-05 19:59:00 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
for i, comp := range completions {
|
|
|
|
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp))
|
2022-03-05 19:59:00 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.PushingNext1(t.Runtime, rt.TableValue(luaComps)), nil
|
|
|
|
}
|
2022-03-05 19:59:00 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
query, ctx, fds, err := getCompleteParams(t, c)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
completions, _ := binaryComplete(query, ctx, fds)
|
|
|
|
luaComps := rt.NewTable()
|
|
|
|
|
|
|
|
for i, comp := range completions {
|
|
|
|
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp))
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.PushingNext1(t.Runtime, rt.TableValue(luaComps)), nil
|
2022-03-05 19:59:00 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func getCompleteParams(t *rt.Thread, c *rt.GoCont) (string, string, []string, error) {
|
|
|
|
if err := c.CheckNArgs(3); err != nil {
|
|
|
|
return "", "", []string{}, err
|
|
|
|
}
|
|
|
|
query, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", []string{}, err
|
|
|
|
}
|
|
|
|
ctx, err := c.StringArg(1)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", []string{}, err
|
|
|
|
}
|
|
|
|
fields, err := c.TableArg(2)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", []string{}, err
|
|
|
|
}
|
2022-03-05 19:59:00 +00:00
|
|
|
|
|
|
|
var fds []string
|
2022-04-04 10:40:02 +00:00
|
|
|
nextVal := rt.NilValue
|
|
|
|
for {
|
|
|
|
next, val, ok := fields.Next(nextVal)
|
|
|
|
if next == rt.NilValue {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
nextVal = next
|
2022-03-05 19:59:00 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
valStr, ok := val.TryString()
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
2022-03-05 19:59:00 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
fds = append(fds, valStr)
|
2022-03-05 19:59:00 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return query, ctx, fds, err
|
2022-03-05 19:59:00 +00:00
|
|
|
}
|
|
|
|
|
2022-03-02 02:00:46 +00:00
|
|
|
func setVimMode(mode string) {
|
2022-04-04 10:40:02 +00:00
|
|
|
util.SetField(l, hshMod, "vimMode", rt.StringValue(mode), "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
2022-03-06 16:08:00 +00:00
|
|
|
hooks.Em.Emit("hilbish.vimMode", mode)
|
2022-03-02 02:00:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func unsetVimMode() {
|
2022-04-04 10:40:02 +00:00
|
|
|
util.SetField(l, hshMod, "vimMode", rt.NilValue, "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
2022-03-02 02:00:46 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
// run(cmd, returnOut) -> exitCode, stdout, stderr
|
2021-12-14 00:13:17 +00:00
|
|
|
// Runs `cmd` in Hilbish's sh interpreter.
|
2022-04-04 10:40:02 +00:00
|
|
|
// If returnOut is true, the outputs of `cmd` will be returned as the 2nd and
|
|
|
|
// 3rd values instead of being outputted to the terminal.
|
2022-02-26 15:36:04 +00:00
|
|
|
// --- @param cmd string
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cmd, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var terminalOut bool
|
|
|
|
if len(c.Etc()) != 0 {
|
|
|
|
tout := c.Etc()[0]
|
|
|
|
termOut, ok := tout.TryBool()
|
|
|
|
terminalOut = termOut
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("bad argument to run (expected boolean, got " + tout.TypeName() + ")")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
terminalOut = true
|
|
|
|
}
|
|
|
|
|
2021-12-07 21:27:07 +00:00
|
|
|
var exitcode uint8
|
2022-04-04 10:40:02 +00:00
|
|
|
stdout, stderr, err := execCommand(cmd, terminalOut)
|
2021-05-16 21:13:28 +00:00
|
|
|
|
|
|
|
if code, ok := interp.IsExitStatus(err); ok {
|
|
|
|
exitcode = code
|
|
|
|
} else if err != nil {
|
|
|
|
exitcode = 1
|
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
stdoutStr := ""
|
|
|
|
stderrStr := ""
|
|
|
|
if !terminalOut {
|
|
|
|
stdoutStr = stdout.(*bytes.Buffer).String()
|
|
|
|
stderrStr = stderr.(*bytes.Buffer).String()
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.PushingNext(t.Runtime, rt.IntValue(int64(exitcode)), rt.StringValue(stdoutStr), rt.StringValue(stderrStr)), nil
|
2021-05-16 19:53:21 +00:00
|
|
|
}
|
|
|
|
|
2021-10-16 16:40:53 +00:00
|
|
|
// cwd()
|
|
|
|
// Returns the current directory of the shell
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2021-05-27 23:06:17 +00:00
|
|
|
cwd, _ := os.Getwd()
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.PushingNext1(t.Runtime, rt.StringValue(cwd)), nil
|
2021-05-27 23:06:17 +00:00
|
|
|
}
|
|
|
|
|
2021-10-30 23:53:42 +00:00
|
|
|
|
|
|
|
// read(prompt) -> input?
|
|
|
|
// Read input from the user, using Hilbish's line editor/input reader.
|
|
|
|
// This is a separate instance from the one Hilbish actually uses.
|
|
|
|
// Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen)
|
2022-02-26 15:36:04 +00:00
|
|
|
// --- @param prompt string
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
luaprompt, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-03-06 18:38:27 +00:00
|
|
|
lualr := newLineReader("", true)
|
2022-03-06 18:23:41 +00:00
|
|
|
lualr.SetPrompt(luaprompt)
|
2021-10-30 23:53:42 +00:00
|
|
|
|
|
|
|
input, err := lualr.Read()
|
|
|
|
if err != nil {
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2021-10-30 23:53:42 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.PushingNext1(t.Runtime, rt.StringValue(input)), nil
|
2021-10-30 23:53:42 +00:00
|
|
|
}
|
|
|
|
|
2022-02-26 15:36:04 +00:00
|
|
|
/*
|
|
|
|
prompt(str)
|
2022-01-26 19:51:52 +00:00
|
|
|
Changes the shell prompt to `str`
|
|
|
|
There are a few verbs that can be used in the prompt text.
|
|
|
|
These will be formatted and replaced with the appropriate values.
|
|
|
|
`%d` - Current working directory
|
|
|
|
`%u` - Name of current user
|
2022-02-26 15:36:04 +00:00
|
|
|
`%h` - Hostname of device
|
|
|
|
--- @param str string
|
|
|
|
*/
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
var prompt string
|
|
|
|
err := c.Check1Arg()
|
|
|
|
if err == nil {
|
|
|
|
prompt, err = c.StringArg(0)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-03-05 01:54:47 +00:00
|
|
|
lr.SetPrompt(fmtPrompt(prompt))
|
2022-01-26 19:51:52 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// multiprompt(str)
|
|
|
|
// Changes the continued line prompt to `str`
|
2022-02-26 15:36:04 +00:00
|
|
|
// --- @param str string
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlmultiprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
prompt, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
multilinePrompt = prompt
|
2022-01-26 19:51:52 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// alias(cmd, orig)
|
2022-03-20 21:54:02 +00:00
|
|
|
// Sets an alias of `cmd` to `orig`
|
2022-02-26 15:36:04 +00:00
|
|
|
// --- @param cmd string
|
|
|
|
// --- @param orig string
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlalias(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.CheckNArgs(2); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cmd, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
orig, err := c.StringArg(1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-01-26 19:51:52 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
aliases.Add(cmd, orig)
|
2022-01-26 19:51:52 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// appendPath(dir)
|
|
|
|
// Appends `dir` to $PATH
|
2022-02-26 15:36:04 +00:00
|
|
|
// --- @param dir string|table
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlappendPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
arg := c.Arg(0)
|
|
|
|
|
2022-01-26 19:51:52 +00:00
|
|
|
// check if dir is a table or a string
|
2022-04-04 10:40:02 +00:00
|
|
|
if arg.Type() == rt.TableType {
|
|
|
|
nextVal := rt.NilValue
|
|
|
|
for {
|
|
|
|
next, val, ok := arg.AsTable().Next(nextVal)
|
|
|
|
if next == rt.NilValue {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
nextVal = next
|
|
|
|
|
|
|
|
valStr, ok := val.TryString()
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
appendPath(valStr)
|
|
|
|
}
|
|
|
|
} else if arg.Type() == rt.StringType {
|
|
|
|
appendPath(arg.AsString())
|
2022-01-26 19:51:52 +00:00
|
|
|
} else {
|
2022-04-04 10:40:02 +00:00
|
|
|
return nil, errors.New("bad argument to appendPath (expected string or table, got " + arg.TypeName() + ")")
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func appendPath(dir string) {
|
|
|
|
dir = strings.Replace(dir, "~", curuser.HomeDir, 1)
|
|
|
|
pathenv := os.Getenv("PATH")
|
|
|
|
|
|
|
|
// if dir isnt already in $PATH, add it
|
|
|
|
if !strings.Contains(pathenv, dir) {
|
|
|
|
os.Setenv("PATH", pathenv + string(os.PathListSeparator) + dir)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// exec(cmd)
|
|
|
|
// Replaces running hilbish with `cmd`
|
2022-02-26 15:36:04 +00:00
|
|
|
// --- @param cmd string
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlexec(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cmd, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-01-26 19:51:52 +00:00
|
|
|
cmdArgs, _ := splitInput(cmd)
|
2022-03-02 02:14:55 +00:00
|
|
|
if runtime.GOOS != "windows" {
|
|
|
|
cmdPath, err := exec.LookPath(cmdArgs[0])
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
// if we get here, cmdPath will be nothing
|
|
|
|
// therefore nothing will run
|
|
|
|
}
|
|
|
|
|
|
|
|
// syscall.Exec requires an absolute path to a binary
|
|
|
|
// path, args, string slice of environments
|
|
|
|
syscall.Exec(cmdPath, cmdArgs, os.Environ())
|
|
|
|
} else {
|
|
|
|
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
cmd.Stdin = os.Stdin
|
|
|
|
cmd.Run()
|
|
|
|
os.Exit(0)
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// goro(fn)
|
|
|
|
// Puts `fn` in a goroutine
|
2022-02-26 15:36:04 +00:00
|
|
|
// --- @param fn function
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlgoro(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fn, err := c.ClosureArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// call fn
|
|
|
|
go func() {
|
2022-04-04 10:40:02 +00:00
|
|
|
_, err := rt.Call1(l.MainThread(), rt.FunctionValue(fn), c.Etc()...)
|
|
|
|
if err != nil {
|
2022-03-05 02:21:34 +00:00
|
|
|
fmt.Fprintln(os.Stderr, "Error in goro function:\n\n", err)
|
|
|
|
}
|
2022-01-26 19:51:52 +00:00
|
|
|
}()
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// timeout(cb, time)
|
|
|
|
// Runs the `cb` function after `time` in milliseconds
|
2022-04-12 23:37:15 +00:00
|
|
|
// Returns a `timer` object (see `doc timers`).
|
2022-02-26 15:36:04 +00:00
|
|
|
// --- @param cb function
|
|
|
|
// --- @param time number
|
2022-04-12 23:37:15 +00:00
|
|
|
// --- @return table
|
2022-04-04 10:40:02 +00:00
|
|
|
func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.CheckNArgs(2); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cb, err := c.ClosureArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
ms, err := c.IntArg(1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-01-26 19:51:52 +00:00
|
|
|
|
2022-04-12 23:28:25 +00:00
|
|
|
interval := time.Duration(ms) * time.Millisecond
|
|
|
|
timer := timers.create(timerTimeout, interval, cb)
|
|
|
|
timer.start()
|
|
|
|
|
|
|
|
return c.PushingNext1(t.Runtime, timer.lua()), nil
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// interval(cb, time)
|
2022-04-12 23:37:15 +00:00
|
|
|
// Runs the `cb` function every `time` milliseconds.
|
|
|
|
// Returns a `timer` object (see `doc timers`).
|
2022-02-26 15:36:04 +00:00
|
|
|
// --- @param cb function
|
|
|
|
// --- @param time number
|
2022-04-12 23:37:15 +00:00
|
|
|
// --- @return table
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.CheckNArgs(2); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cb, err := c.ClosureArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
ms, err := c.IntArg(1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-01-26 19:51:52 +00:00
|
|
|
|
2022-04-12 23:28:25 +00:00
|
|
|
interval := time.Duration(ms) * time.Millisecond
|
2022-04-12 23:37:15 +00:00
|
|
|
timer := timers.creat(timerInterval, interval, cb)
|
2022-04-12 23:28:25 +00:00
|
|
|
timer.start()
|
2022-01-26 19:51:52 +00:00
|
|
|
|
2022-04-12 23:28:25 +00:00
|
|
|
return c.PushingNext1(t.Runtime, timer.lua()), nil
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// complete(scope, cb)
|
|
|
|
// Registers a completion handler for `scope`.
|
|
|
|
// A `scope` is currently only expected to be `command.<cmd>`,
|
|
|
|
// replacing <cmd> with the name of the command (for example `command.git`).
|
2022-03-05 20:12:46 +00:00
|
|
|
// `cb` must be a function that returns a table of "completion groups."
|
|
|
|
// A completion group is a table with the keys `items` and `type`.
|
|
|
|
// `items` being a table of items and `type` being the display type of
|
|
|
|
// `grid` (the normal file completion display) or `list` (with a description)
|
2022-03-02 02:12:48 +00:00
|
|
|
// --- @param scope string
|
|
|
|
// --- @param cb function
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlcomplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
scope, cb, err := util.HandleStrCallback(t, c)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-01-26 19:51:52 +00:00
|
|
|
luaCompletions[scope] = cb
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// prependPath(dir)
|
|
|
|
// Prepends `dir` to $PATH
|
2022-03-02 02:12:48 +00:00
|
|
|
// --- @param dir string
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
dir, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-01-26 19:51:52 +00:00
|
|
|
dir = strings.Replace(dir, "~", curuser.HomeDir, 1)
|
|
|
|
pathenv := os.Getenv("PATH")
|
|
|
|
|
|
|
|
// if dir isnt already in $PATH, add in
|
|
|
|
if !strings.Contains(pathenv, dir) {
|
|
|
|
os.Setenv("PATH", dir + string(os.PathListSeparator) + pathenv)
|
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-01-26 19:51:52 +00:00
|
|
|
}
|
2022-03-01 22:59:44 +00:00
|
|
|
|
|
|
|
// which(binName)
|
|
|
|
// Searches for an executable called `binName` in the directories of $PATH
|
2022-03-02 02:12:48 +00:00
|
|
|
// --- @param binName string
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
binName, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-03-01 22:59:44 +00:00
|
|
|
path, err := exec.LookPath(binName)
|
|
|
|
if err != nil {
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-03-01 22:59:44 +00:00
|
|
|
}
|
2022-03-05 19:46:38 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.PushingNext1(t.Runtime, rt.StringValue(path)), nil
|
2022-03-01 22:59:44 +00:00
|
|
|
}
|
2022-03-02 02:00:46 +00:00
|
|
|
|
|
|
|
// inputMode(mode)
|
|
|
|
// Sets the input mode for Hilbish's line reader. Accepts either emacs for vim
|
2022-03-02 02:12:48 +00:00
|
|
|
// --- @param mode string
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
mode, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-03-02 02:00:46 +00:00
|
|
|
switch mode {
|
|
|
|
case "emacs":
|
|
|
|
unsetVimMode()
|
|
|
|
lr.rl.InputMode = readline.Emacs
|
|
|
|
case "vim":
|
|
|
|
setVimMode("insert")
|
|
|
|
lr.rl.InputMode = readline.Vim
|
2022-04-04 10:40:02 +00:00
|
|
|
default:
|
|
|
|
return nil, errors.New("inputMode: expected vim or emacs, received " + mode)
|
2022-03-02 02:00:46 +00:00
|
|
|
}
|
2022-04-04 10:40:02 +00:00
|
|
|
|
|
|
|
return c.Next(), nil
|
2022-03-02 02:00:46 +00:00
|
|
|
}
|
2022-03-20 19:15:44 +00:00
|
|
|
|
|
|
|
// runnerMode(mode)
|
|
|
|
// Sets the execution/runner mode for interactive Hilbish. This determines whether
|
|
|
|
// Hilbish wll try to run input as Lua and/or sh or only do one of either.
|
|
|
|
// Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
|
|
|
// sh, and lua. It also accepts a function, to which if it is passed one
|
|
|
|
// will call it to execute user input instead.
|
2022-03-22 22:33:11 +00:00
|
|
|
// --- @param mode string|function
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
mode := c.Arg(0)
|
|
|
|
|
2022-03-20 19:15:44 +00:00
|
|
|
switch mode.Type() {
|
2022-04-04 10:40:02 +00:00
|
|
|
case rt.StringType:
|
|
|
|
switch mode.AsString() {
|
2022-03-20 19:15:44 +00:00
|
|
|
// no fallthrough doesnt work so eh
|
2022-04-04 10:40:02 +00:00
|
|
|
case "hybrid", "hybridRev", "lua", "sh": runnerMode = mode
|
|
|
|
default: return nil, errors.New("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received " + mode.AsString())
|
2022-03-20 19:15:44 +00:00
|
|
|
}
|
2022-04-04 10:40:02 +00:00
|
|
|
case rt.FunctionType: runnerMode = mode
|
|
|
|
default: return nil, errors.New("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received " + mode.TypeName())
|
2022-03-20 19:15:44 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-03-20 19:15:44 +00:00
|
|
|
}
|
2022-03-26 21:34:09 +00:00
|
|
|
|
|
|
|
// hinter(cb)
|
|
|
|
// Sets the hinter function. This will be called on every key insert to determine
|
|
|
|
// what text to use as an inline hint. The callback is passed 2 arguments:
|
|
|
|
// the current line and the position. It is expected to return a string
|
|
|
|
// which will be used for the hint.
|
|
|
|
// --- @param cb function
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
hinterCb, err := c.ClosureArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-03-26 21:34:09 +00:00
|
|
|
hinter = hinterCb
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), err
|
2022-03-26 21:34:09 +00:00
|
|
|
}
|
2022-03-26 22:25:19 +00:00
|
|
|
|
2022-03-26 22:28:01 +00:00
|
|
|
// highlighter(cb)
|
|
|
|
// Sets the highlighter function. This is mainly for syntax hightlighting, but in
|
|
|
|
// reality could set the input of the prompt to display anything. The callback
|
|
|
|
// is passed the current line as typed and is expected to return a line that will
|
|
|
|
// be used to display in the line.
|
|
|
|
// --- @param cb function
|
2022-04-04 10:40:02 +00:00
|
|
|
func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
highlighterCb, err := c.ClosureArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-03-26 22:25:19 +00:00
|
|
|
highlighter = highlighterCb
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), err
|
2022-03-26 22:25:19 +00:00
|
|
|
}
|