mirror of https://github.com/Hilbis/Hilbish
Compare commits
6 Commits
eb0a81f7a2
...
1e899bf18e
Author | SHA1 | Date |
---|---|---|
TorchedSammy | 1e899bf18e | |
TorchedSammy | f03f8c0da1 | |
TorchedSammy | 1378a74e87 | |
TorchedSammy | 63bc398f1c | |
TorchedSammy | 579a0cd0ce | |
TorchedSammy | f433ab8a6f |
62
aliases.go
62
aliases.go
|
@ -7,54 +7,54 @@ import (
|
||||||
"github.com/yuin/gopher-lua"
|
"github.com/yuin/gopher-lua"
|
||||||
)
|
)
|
||||||
|
|
||||||
var aliases *hilbishAliases
|
var aliases *aliasHandler
|
||||||
|
|
||||||
type hilbishAliases struct {
|
type aliasHandler struct {
|
||||||
aliases map[string]string
|
aliases map[string]string
|
||||||
mu *sync.RWMutex
|
mu *sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize aliases map
|
// initialize aliases map
|
||||||
func NewAliases() *hilbishAliases {
|
func newAliases() *aliasHandler {
|
||||||
return &hilbishAliases{
|
return &aliasHandler{
|
||||||
aliases: make(map[string]string),
|
aliases: make(map[string]string),
|
||||||
mu: &sync.RWMutex{},
|
mu: &sync.RWMutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) Add(alias, cmd string) {
|
func (a *aliasHandler) Add(alias, cmd string) {
|
||||||
h.mu.Lock()
|
a.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
|
|
||||||
h.aliases[alias] = cmd
|
a.aliases[alias] = cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) All() map[string]string {
|
func (a *aliasHandler) All() map[string]string {
|
||||||
return h.aliases
|
return a.aliases
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) Delete(alias string) {
|
func (a *aliasHandler) Delete(alias string) {
|
||||||
h.mu.Lock()
|
a.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
|
|
||||||
delete(h.aliases, alias)
|
delete(a.aliases, alias)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) Resolve(cmdstr string) string {
|
func (a *aliasHandler) Resolve(cmdstr string) string {
|
||||||
h.mu.RLock()
|
a.mu.RLock()
|
||||||
defer h.mu.RUnlock()
|
defer a.mu.RUnlock()
|
||||||
|
|
||||||
args := strings.Split(cmdstr, " ")
|
args := strings.Split(cmdstr, " ")
|
||||||
for h.aliases[args[0]] != "" {
|
for a.aliases[args[0]] != "" {
|
||||||
alias := h.aliases[args[0]]
|
alias := a.aliases[args[0]]
|
||||||
cmdstr = alias + strings.TrimPrefix(cmdstr, args[0])
|
cmdstr = alias + strings.TrimPrefix(cmdstr, args[0])
|
||||||
cmdArgs, _ := splitInput(cmdstr)
|
cmdArgs, _ := splitInput(cmdstr)
|
||||||
args = cmdArgs
|
args = cmdArgs
|
||||||
|
|
||||||
if h.aliases[args[0]] == alias {
|
if a.aliases[args[0]] == alias {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if h.aliases[args[0]] != "" {
|
if a.aliases[args[0]] != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,12 +64,12 @@ func (h *hilbishAliases) Resolve(cmdstr string) string {
|
||||||
|
|
||||||
// lua section
|
// lua section
|
||||||
|
|
||||||
func (h *hilbishAliases) Loader(L *lua.LState) *lua.LTable {
|
func (a *aliasHandler) Loader(L *lua.LState) *lua.LTable {
|
||||||
// create a lua module with our functions
|
// create a lua module with our functions
|
||||||
hshaliasesLua := map[string]lua.LGFunction{
|
hshaliasesLua := map[string]lua.LGFunction{
|
||||||
"add": h.luaAdd,
|
"add": a.luaAdd,
|
||||||
"list": h.luaList,
|
"list": a.luaList,
|
||||||
"del": h.luaDelete,
|
"del": a.luaDelete,
|
||||||
}
|
}
|
||||||
|
|
||||||
mod := L.SetFuncs(L.NewTable(), hshaliasesLua)
|
mod := L.SetFuncs(L.NewTable(), hshaliasesLua)
|
||||||
|
@ -77,17 +77,17 @@ func (h *hilbishAliases) Loader(L *lua.LState) *lua.LTable {
|
||||||
return mod
|
return mod
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) luaAdd(L *lua.LState) int {
|
func (a *aliasHandler) luaAdd(L *lua.LState) int {
|
||||||
alias := L.CheckString(1)
|
alias := L.CheckString(1)
|
||||||
cmd := L.CheckString(2)
|
cmd := L.CheckString(2)
|
||||||
h.Add(alias, cmd)
|
a.Add(alias, cmd)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) luaList(L *lua.LState) int {
|
func (a *aliasHandler) luaList(L *lua.LState) int {
|
||||||
aliasesList := L.NewTable()
|
aliasesList := L.NewTable()
|
||||||
for k, v := range h.All() {
|
for k, v := range a.All() {
|
||||||
aliasesList.RawSetString(k, lua.LString(v))
|
aliasesList.RawSetString(k, lua.LString(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,9 +96,9 @@ func (h *hilbishAliases) luaList(L *lua.LState) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) luaDelete(L *lua.LState) int {
|
func (a *aliasHandler) luaDelete(L *lua.LState) int {
|
||||||
alias := L.CheckString(1)
|
alias := L.CheckString(1)
|
||||||
h.Delete(alias)
|
a.Delete(alias)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
4
api.go
4
api.go
|
@ -88,7 +88,7 @@ Check out the {blue}{bold}guide{reset} command to get started.
|
||||||
L.SetField(mod, "os", hshos)
|
L.SetField(mod, "os", hshos)
|
||||||
|
|
||||||
// hilbish.aliases table
|
// hilbish.aliases table
|
||||||
aliases = NewAliases()
|
aliases = newAliases()
|
||||||
aliasesModule := aliases.Loader(L)
|
aliasesModule := aliases.Loader(L)
|
||||||
util.Document(L, aliasesModule, "Alias inferface for Hilbish.")
|
util.Document(L, aliasesModule, "Alias inferface for Hilbish.")
|
||||||
L.SetField(mod, "aliases", aliasesModule)
|
L.SetField(mod, "aliases", aliasesModule)
|
||||||
|
@ -106,6 +106,8 @@ Check out the {blue}{bold}guide{reset} command to get started.
|
||||||
util.Document(L, hshcomp, "Completions interface for Hilbish.")
|
util.Document(L, hshcomp, "Completions interface for Hilbish.")
|
||||||
L.SetField(mod, "completion", hshcomp)
|
L.SetField(mod, "completion", hshcomp)
|
||||||
|
|
||||||
|
jobs = newJobHandler()
|
||||||
|
|
||||||
L.Push(mod)
|
L.Push(mod)
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
Note: A `job` is a table with the following keys:
|
||||||
|
- cmd: command string
|
||||||
|
- running: boolean whether the job is running
|
||||||
|
- id: unique id for the job
|
||||||
|
- pid: process id for the job
|
||||||
|
- exitCode: exit code of the job
|
||||||
|
In ordinary cases you'd prefer to use the id instead of pid. The id is unique to
|
||||||
|
Hilbish and is how you get jobs with the `hilbish.jobs` interface.
|
||||||
|
|
||||||
|
+ `job.start` -> job > Thrown when a new background job starts.
|
||||||
|
|
||||||
|
+ `job.done` -> job > Thrown when a background jobs exits.
|
||||||
|
|
134
exec.go
134
exec.go
|
@ -1,13 +1,16 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"os/exec"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hilbish/util"
|
"hilbish/util"
|
||||||
|
@ -17,6 +20,7 @@ import (
|
||||||
//"github.com/yuin/gopher-lua/parse"
|
//"github.com/yuin/gopher-lua/parse"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errNotExec = errors.New("not executable")
|
var errNotExec = errors.New("not executable")
|
||||||
|
@ -92,6 +96,7 @@ func execCommand(cmd, old string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bg bool
|
||||||
exechandle := func(ctx context.Context, args []string) error {
|
exechandle := func(ctx context.Context, args []string) error {
|
||||||
_, argstring := splitInput(strings.Join(args, " "))
|
_, argstring := splitInput(strings.Join(args, " "))
|
||||||
// i dont really like this but it works
|
// i dont really like this but it works
|
||||||
|
@ -150,15 +155,138 @@ func execCommand(cmd, old string) error {
|
||||||
return interp.NewExitStatus(127)
|
return interp.NewExitStatus(127)
|
||||||
}
|
}
|
||||||
|
|
||||||
return interp.DefaultExecHandler(2 * time.Second)(ctx, args)
|
killTimeout := 2 * time.Second
|
||||||
|
// from here is basically copy-paste of the default exec handler from
|
||||||
|
// sh/interp but with our job handling
|
||||||
|
hc := interp.HandlerCtx(ctx)
|
||||||
|
path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(hc.Stderr, err)
|
||||||
|
return interp.NewExitStatus(127)
|
||||||
|
}
|
||||||
|
|
||||||
|
env := hc.Env
|
||||||
|
envList := make([]string, 0, 64)
|
||||||
|
env.Each(func(name string, vr expand.Variable) bool {
|
||||||
|
if !vr.IsSet() {
|
||||||
|
// If a variable is set globally but unset in the
|
||||||
|
// runner, we need to ensure it's not part of the final
|
||||||
|
// list. Seems like zeroing the element is enough.
|
||||||
|
// This is a linear search, but this scenario should be
|
||||||
|
// rare, and the number of variables shouldn't be large.
|
||||||
|
for i, kv := range envList {
|
||||||
|
if strings.HasPrefix(kv, name+"=") {
|
||||||
|
envList[i] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vr.Exported && vr.Kind == expand.String {
|
||||||
|
envList = append(envList, name+"="+vr.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
cmd := exec.Cmd{
|
||||||
|
Path: path,
|
||||||
|
Args: args,
|
||||||
|
Env: envList,
|
||||||
|
Dir: hc.Dir,
|
||||||
|
Stdin: hc.Stdin,
|
||||||
|
Stdout: hc.Stdout,
|
||||||
|
Stderr: hc.Stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
job := jobs.getLatest()
|
||||||
|
if err == nil {
|
||||||
|
if bg {
|
||||||
|
job.start(cmd.Process.Pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if done := ctx.Done(); done != nil {
|
||||||
|
go func() {
|
||||||
|
<-done
|
||||||
|
|
||||||
|
if killTimeout <= 0 || runtime.GOOS == "windows" {
|
||||||
|
cmd.Process.Signal(os.Kill)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: don't temporarily leak this goroutine
|
||||||
|
// if the program stops itself with the
|
||||||
|
// interrupt.
|
||||||
|
go func() {
|
||||||
|
time.Sleep(killTimeout)
|
||||||
|
cmd.Process.Signal(os.Kill)
|
||||||
|
}()
|
||||||
|
cmd.Process.Signal(os.Interrupt)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
var exit uint8
|
||||||
|
switch x := err.(type) {
|
||||||
|
case *exec.ExitError:
|
||||||
|
// started, but errored - default to 1 if OS
|
||||||
|
// doesn't have exit statuses
|
||||||
|
if status, ok := x.Sys().(syscall.WaitStatus); ok {
|
||||||
|
if status.Signaled() {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
exit = uint8(128 + status.Signal())
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
exit = uint8(status.ExitStatus())
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
exit = 1
|
||||||
|
goto end
|
||||||
|
case *exec.Error:
|
||||||
|
// did not start
|
||||||
|
fmt.Fprintf(hc.Stderr, "%v\n", err)
|
||||||
|
exit = 127
|
||||||
|
goto end
|
||||||
|
case nil:
|
||||||
|
goto end
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
end:
|
||||||
|
if bg {
|
||||||
|
job.exitCode = int(exit)
|
||||||
|
job.finish()
|
||||||
|
}
|
||||||
|
return interp.NewExitStatus(exit)
|
||||||
}
|
}
|
||||||
|
|
||||||
runner, _ := interp.New(
|
runner, _ := interp.New(
|
||||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||||
interp.ExecHandler(exechandle),
|
interp.ExecHandler(exechandle),
|
||||||
)
|
)
|
||||||
err = runner.Run(context.TODO(), file)
|
|
||||||
|
|
||||||
return err
|
buf := new(bytes.Buffer)
|
||||||
|
printer := syntax.NewPrinter()
|
||||||
|
|
||||||
|
for _, stmt := range file.Stmts {
|
||||||
|
bg = false
|
||||||
|
if stmt.Background {
|
||||||
|
bg = true
|
||||||
|
printer.Print(buf, stmt.Cmd)
|
||||||
|
|
||||||
|
stmtStr := buf.String()
|
||||||
|
buf.Reset()
|
||||||
|
jobs.add(stmtStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runner.Run(context.TODO(), stmt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookpath(file string) error { // custom lookpath function so we know if a command is found *and* is executable
|
func lookpath(file string) error { // custom lookpath function so we know if a command is found *and* is executable
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/yuin/gopher-lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
var jobs *jobHandler
|
||||||
|
|
||||||
|
type job struct {
|
||||||
|
cmd string
|
||||||
|
running bool
|
||||||
|
id int
|
||||||
|
pid int
|
||||||
|
exitCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *job) start(pid int) {
|
||||||
|
j.pid = pid
|
||||||
|
j.running = true
|
||||||
|
hooks.Em.Emit("job.start", j.lua())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *job) finish() {
|
||||||
|
j.running = false
|
||||||
|
hooks.Em.Emit("job.done", j.lua())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *job) lua() *lua.LTable {
|
||||||
|
// returns lua table for job
|
||||||
|
// because userdata is gross
|
||||||
|
luaJob := l.NewTable()
|
||||||
|
|
||||||
|
l.SetField(luaJob, "cmd", lua.LString(j.cmd))
|
||||||
|
l.SetField(luaJob, "running", lua.LBool(j.running))
|
||||||
|
l.SetField(luaJob, "id", lua.LNumber(j.id))
|
||||||
|
l.SetField(luaJob, "pid", lua.LNumber(j.pid))
|
||||||
|
l.SetField(luaJob, "exitCode", lua.LNumber(j.exitCode))
|
||||||
|
|
||||||
|
return luaJob
|
||||||
|
}
|
||||||
|
|
||||||
|
type jobHandler struct {
|
||||||
|
jobs map[int]*job
|
||||||
|
latestID int
|
||||||
|
mu *sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newJobHandler() *jobHandler {
|
||||||
|
return &jobHandler{
|
||||||
|
jobs: make(map[int]*job),
|
||||||
|
latestID: 0,
|
||||||
|
mu: &sync.RWMutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *jobHandler) add(cmd string) {
|
||||||
|
j.mu.Lock()
|
||||||
|
defer j.mu.Unlock()
|
||||||
|
|
||||||
|
j.latestID++
|
||||||
|
j.jobs[j.latestID] = &job{
|
||||||
|
cmd: cmd,
|
||||||
|
running: false,
|
||||||
|
id: j.latestID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *jobHandler) getLatest() *job {
|
||||||
|
j.mu.RLock()
|
||||||
|
defer j.mu.RUnlock()
|
||||||
|
|
||||||
|
return j.jobs[j.latestID]
|
||||||
|
}
|
|
@ -168,6 +168,9 @@ hilbish.userDir.config .. '/hilbish/init.lua' ..
|
||||||
and also change all global functions (prompt, alias) to be
|
and also change all global functions (prompt, alias) to be
|
||||||
in the hilbish module (hilbish.prompt, hilbish.alias as examples).
|
in the hilbish module (hilbish.prompt, hilbish.alias as examples).
|
||||||
|
|
||||||
|
And if this is your first time (most likely), you can copy a config
|
||||||
|
from ]] .. hilbish.dataDir,
|
||||||
|
[[
|
||||||
Since 1.0 is a big release, you'll want to check the changelog
|
Since 1.0 is a big release, you'll want to check the changelog
|
||||||
at https://github.com/Rosettea/Hilbish/releases/tag/v1.0.0
|
at https://github.com/Rosettea/Hilbish/releases/tag/v1.0.0
|
||||||
to find more breaking changes.
|
to find more breaking changes.
|
||||||
|
|
2
rl.go
2
rl.go
|
@ -21,7 +21,7 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
||||||
// but it cant have shared history
|
// but it cant have shared history
|
||||||
if !noHist {
|
if !noHist {
|
||||||
fileHist = newFileHistory()
|
fileHist = newFileHistory()
|
||||||
rl.SetHistoryCtrlR("file", fileHist)
|
rl.SetHistoryCtrlR("History", fileHist)
|
||||||
rl.HistoryAutoWrite = false
|
rl.HistoryAutoWrite = false
|
||||||
}
|
}
|
||||||
rl.ShowVimMode = false
|
rl.ShowVimMode = false
|
||||||
|
|
Loading…
Reference in New Issue