Compare commits

..

No commits in common. "1e899bf18e8341c21baef9b88692afb62239058e" and "eb0a81f7a2e598f1c3339ed3cb2c937a231f87b3" have entirely different histories.

7 changed files with 36 additions and 257 deletions

View File

@ -7,54 +7,54 @@ import (
"github.com/yuin/gopher-lua" "github.com/yuin/gopher-lua"
) )
var aliases *aliasHandler var aliases *hilbishAliases
type aliasHandler struct { type hilbishAliases struct {
aliases map[string]string aliases map[string]string
mu *sync.RWMutex mu *sync.RWMutex
} }
// initialize aliases map // initialize aliases map
func newAliases() *aliasHandler { func NewAliases() *hilbishAliases {
return &aliasHandler{ return &hilbishAliases{
aliases: make(map[string]string), aliases: make(map[string]string),
mu: &sync.RWMutex{}, mu: &sync.RWMutex{},
} }
} }
func (a *aliasHandler) Add(alias, cmd string) { func (h *hilbishAliases) Add(alias, cmd string) {
a.mu.Lock() h.mu.Lock()
defer a.mu.Unlock() defer h.mu.Unlock()
a.aliases[alias] = cmd h.aliases[alias] = cmd
} }
func (a *aliasHandler) All() map[string]string { func (h *hilbishAliases) All() map[string]string {
return a.aliases return h.aliases
} }
func (a *aliasHandler) Delete(alias string) { func (h *hilbishAliases) Delete(alias string) {
a.mu.Lock() h.mu.Lock()
defer a.mu.Unlock() defer h.mu.Unlock()
delete(a.aliases, alias) delete(h.aliases, alias)
} }
func (a *aliasHandler) Resolve(cmdstr string) string { func (h *hilbishAliases) Resolve(cmdstr string) string {
a.mu.RLock() h.mu.RLock()
defer a.mu.RUnlock() defer h.mu.RUnlock()
args := strings.Split(cmdstr, " ") args := strings.Split(cmdstr, " ")
for a.aliases[args[0]] != "" { for h.aliases[args[0]] != "" {
alias := a.aliases[args[0]] alias := h.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 a.aliases[args[0]] == alias { if h.aliases[args[0]] == alias {
break break
} }
if a.aliases[args[0]] != "" { if h.aliases[args[0]] != "" {
continue continue
} }
} }
@ -64,12 +64,12 @@ func (a *aliasHandler) Resolve(cmdstr string) string {
// lua section // lua section
func (a *aliasHandler) Loader(L *lua.LState) *lua.LTable { func (h *hilbishAliases) 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": a.luaAdd, "add": h.luaAdd,
"list": a.luaList, "list": h.luaList,
"del": a.luaDelete, "del": h.luaDelete,
} }
mod := L.SetFuncs(L.NewTable(), hshaliasesLua) mod := L.SetFuncs(L.NewTable(), hshaliasesLua)
@ -77,17 +77,17 @@ func (a *aliasHandler) Loader(L *lua.LState) *lua.LTable {
return mod return mod
} }
func (a *aliasHandler) luaAdd(L *lua.LState) int { func (h *hilbishAliases) luaAdd(L *lua.LState) int {
alias := L.CheckString(1) alias := L.CheckString(1)
cmd := L.CheckString(2) cmd := L.CheckString(2)
a.Add(alias, cmd) h.Add(alias, cmd)
return 0 return 0
} }
func (a *aliasHandler) luaList(L *lua.LState) int { func (h *hilbishAliases) luaList(L *lua.LState) int {
aliasesList := L.NewTable() aliasesList := L.NewTable()
for k, v := range a.All() { for k, v := range h.All() {
aliasesList.RawSetString(k, lua.LString(v)) aliasesList.RawSetString(k, lua.LString(v))
} }
@ -96,9 +96,9 @@ func (a *aliasHandler) luaList(L *lua.LState) int {
return 1 return 1
} }
func (a *aliasHandler) luaDelete(L *lua.LState) int { func (h *hilbishAliases) luaDelete(L *lua.LState) int {
alias := L.CheckString(1) alias := L.CheckString(1)
a.Delete(alias) h.Delete(alias)
return 0 return 0
} }

4
api.go
View File

@ -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,8 +106,6 @@ 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

View File

@ -1,13 +0,0 @@
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
View File

@ -1,16 +1,13 @@
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"
@ -20,7 +17,6 @@ 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")
@ -96,7 +92,6 @@ 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
@ -155,138 +150,15 @@ func execCommand(cmd, old string) error {
return interp.NewExitStatus(127) return interp.NewExitStatus(127)
} }
killTimeout := 2 * time.Second return interp.DefaultExecHandler(2 * time.Second)(ctx, args)
// 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)
buf := new(bytes.Buffer) return err
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

75
job.go
View File

@ -1,75 +0,0 @@
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]
}

View File

@ -168,9 +168,6 @@ 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
View File

@ -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("History", fileHist) rl.SetHistoryCtrlR("file", fileHist)
rl.HistoryAutoWrite = false rl.HistoryAutoWrite = false
} }
rl.ShowVimMode = false rl.ShowVimMode = false