Compare commits

...

2 Commits

Author SHA1 Message Date
sammyette 66419be0b0
Merge f239363a60 into a0513c0a05 2023-12-18 23:46:57 +00:00
sammyette f239363a60
feat: suspend jobs via ctrl z
this was a bit of a pain to add and it still doesnt work
properly on this commit, but this is how it would be done
2023-02-19 15:13:52 -04:00
6 changed files with 107 additions and 18 deletions

22
api.go
View File

@ -48,6 +48,7 @@ var exports = map[string]util.LuaExport{
"interval": {hlinterval, 2, false}, "interval": {hlinterval, 2, false},
"read": {hlread, 1, false}, "read": {hlread, 1, false},
"run": {hlrun, 1, true}, "run": {hlrun, 1, true},
"suspend": {hlsuspend, 0, false},
"timeout": {hltimeout, 2, false}, "timeout": {hltimeout, 2, false},
"which": {hlwhich, 1, false}, "which": {hlwhich, 1, false},
} }
@ -89,6 +90,12 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
if err != nil { if err != nil {
return nil, errors.New("hilbish.hinter has to be a function") return nil, errors.New("hilbish.hinter has to be a function")
} }
} else if k == "suspend" {
var err error
_, err = c.ClosureArg(2)
if err != nil {
return nil, errors.New("hilbish.suspend has to be a function")
}
} else if modVal := mod.Get(rt.StringValue(k)); modVal != rt.NilValue { } else if modVal := mod.Get(rt.StringValue(k)); modVal != rt.NilValue {
return nil, errors.New("not allowed to override in hilbish table") return nil, errors.New("not allowed to override in hilbish table")
} }
@ -220,7 +227,7 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
var exitcode uint8 var exitcode uint8
stdout, stderr, err := execCommand(cmd, terminalOut) stdout, stderr, err := execCommand(cmd, terminalOut, false)
if code, ok := interp.IsExitStatus(err); ok { if code, ok := interp.IsExitStatus(err); ok {
exitcode = code exitcode = code
@ -658,3 +665,16 @@ func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil return c.Next(), nil
} }
// suspend()
// Suspends the currently running process. This can (and should be) overwritten if a
// a different runner is being used.
// --- @param line string
func hlsuspend(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if currentHandle != nil || currentHandle.Process != nil {
j := jobs.addFromHandler(currentCmd, currentHandle)
j.suspend()
}
return c.Next(), nil
}

52
exec.go
View File

@ -27,6 +27,8 @@ import (
var errNotExec = errors.New("not executable") var errNotExec = errors.New("not executable")
var errNotFound = errors.New("not found") var errNotFound = errors.New("not found")
var runnerMode rt.Value = rt.StringValue("hybrid") var runnerMode rt.Value = rt.StringValue("hybrid")
var currentHandle *exec.Cmd
var currentCmd string
type execError struct{ type execError struct{
typ string typ string
@ -236,7 +238,7 @@ func handleSh(cmdString string) (input string, exitCode uint8, cont bool, runErr
} }
func execSh(cmdString string) (string, uint8, bool, error) { func execSh(cmdString string) (string, uint8, bool, error) {
_, _, err := execCommand(cmdString, true) _, _, err := execCommand(cmdString, true, true)
if err != nil { if err != nil {
// If input is incomplete, start multiline prompting // If input is incomplete, start multiline prompting
if syntax.IsIncomplete(err) { if syntax.IsIncomplete(err) {
@ -257,7 +259,7 @@ func execSh(cmdString string) (string, uint8, bool, error) {
} }
// Run command in sh interpreter // Run command in sh interpreter
func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) { func execCommand(cmd string, terminalOut, interactiveCall bool) (io.Writer, io.Writer, error) {
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "") file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -280,16 +282,21 @@ func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
var bg bool var bg bool
for _, stmt := range file.Stmts { for _, stmt := range file.Stmts {
bg = false bg = false
printer.Print(buf, stmt.Cmd)
stmtStr := buf.String()
buf.Reset()
if interactiveCall {
currentCmd = stmtStr
}
if stmt.Background { if stmt.Background {
bg = true bg = true
printer.Print(buf, stmt.Cmd)
stmtStr := buf.String()
buf.Reset()
jobs.add(stmtStr, []string{}, "") jobs.add(stmtStr, []string{}, "")
} }
interp.ExecHandler(execHandle(bg))(runner) interp.ExecHandler(execHandle(bg, interactiveCall))(runner)
err = runner.Run(context.TODO(), stmt) err = runner.Run(context.TODO(), stmt)
if err != nil { if err != nil {
return stdout, stderr, err return stdout, stderr, err
@ -299,7 +306,7 @@ func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
return stdout, stderr, nil return stdout, stderr, nil
} }
func execHandle(bg bool) interp.ExecHandlerFunc { func execHandle(bg, interactiveCall bool) interp.ExecHandlerFunc {
return func(ctx context.Context, args []string) error { return 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
@ -413,6 +420,10 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
Stderr: hc.Stderr, Stderr: hc.Stderr,
} }
if interactiveCall {
currentHandle = &cmd
}
var j *job var j *job
if bg { if bg {
j = jobs.getLatest() j = jobs.getLatest()
@ -422,6 +433,8 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
err = cmd.Start() err = cmd.Start()
} }
execDone := make(chan struct{}, 1)
var suspended bool
if err == nil { if err == nil {
if done := ctx.Done(); done != nil { if done := ctx.Done(); done != nil {
go func() { go func() {
@ -443,12 +456,31 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
}() }()
} }
err = cmd.Wait() go func() {
hooks.On("job.suspend", func(args ...interface{}) {
fmt.Println("suspended")
val := args[0].(rt.Value)
_, ok := valueToJob(val)
if !ok {
return
}
suspended = true
execDone <- struct{}{}
})
}()
go func() {
err = cmd.Wait()
execDone <- struct{}{}
}()
<-execDone
} }
exit := handleExecErr(err) exit := handleExecErr(err)
if bg { if bg && !suspended {
j.exitCode = int(exit) j.exitCode = int(exit)
j.finish() j.finish()
} }

12
job.go
View File

@ -268,6 +268,18 @@ func (j *jobHandler) add(cmd string, args []string, path string) *job {
return jb return jb
} }
func (j *jobHandler) addFromHandler(cmd string, handler *exec.Cmd) *job {
jb := j.add(cmd, handler.Args, handler.Path)
if ps := handler.ProcessState; ps != nil {
if !ps.Exited() && ps.Pid() != 0 {
jb.running = true
}
}
jb.setHandle(handler)
return jb
}
func (j *jobHandler) getLatest() *job { func (j *jobHandler) getLatest() *job {
j.mu.RLock() j.mu.RLock()
defer j.mu.RUnlock() defer j.mu.RUnlock()

View File

@ -8,6 +8,7 @@ import (
"syscall" "syscall"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
rt "github.com/arnodel/golua/runtime"
) )
func (j *job) foreground() error { func (j *job) foreground() error {
@ -36,3 +37,14 @@ func (j *job) background() error {
proc.Signal(syscall.SIGCONT) proc.Signal(syscall.SIGCONT)
return nil return nil
} }
func (j *job) suspend() error {
proc := j.handle.Process
if proc == nil {
return nil
}
proc.Signal(syscall.SIGSTOP)
hooks.Emit("job.suspend", rt.UserDataValue(j.ud))
return nil
}

View File

@ -13,3 +13,7 @@ func (j *job) foreground() error {
func (j *job) background() error { func (j *job) background() error {
return errors.New("not supported on windows") return errors.New("not supported on windows")
} }
func (j *job) suspend() error {
return errors.New("not supported on windows")
}

View File

@ -3,23 +3,32 @@
package main package main
import ( import (
"fmt"
"syscall" "syscall"
"os" "os"
"os/signal" "os/signal"
rt "github.com/arnodel/golua/runtime"
) )
func handleSignals() { func handleSignals() {
c := make(chan os.Signal) c := make(chan os.Signal)
signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN, syscall.SIGTSTP) signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN)
signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGWINCH, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGQUIT) signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGWINCH, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGQUIT, syscall.SIGTSTP)
for s := range c { for s := range c {
switch s { switch s {
case os.Interrupt: hooks.Emit("signal.sigint") case os.Interrupt: hooks.Emit("signal.sigint")
case syscall.SIGTERM: exit(0) case syscall.SIGTERM: exit(0)
case syscall.SIGWINCH: hooks.Emit("signal.resize") case syscall.SIGWINCH: hooks.Emit("signal.resize")
case syscall.SIGUSR1: hooks.Emit("signal.sigusr1") case syscall.SIGUSR1: hooks.Emit("signal.sigusr1")
case syscall.SIGUSR2: hooks.Emit("signal.sigusr2") case syscall.SIGUSR2: hooks.Emit("signal.sigusr2")
case syscall.SIGTSTP:
suspendHandler := hshMod.Get(rt.StringValue("suspend"))
_, err := rt.Call1(l.MainThread(), suspendHandler)
if err != nil {
fmt.Println(err)
}
} }
} }
} }