diff --git a/api.go b/api.go index 3920e6f..efc3a01 100644 --- a/api.go +++ b/api.go @@ -47,6 +47,7 @@ var exports = map[string]util.LuaExport{ "interval": {hlinterval, 2, false}, "read": {hlread, 1, false}, "run": {hlrun, 1, true}, + "suspend": {hlsuspend, 0, false}, "timeout": {hltimeout, 2, false}, "which": {hlwhich, 1, false}, } @@ -88,6 +89,12 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) { if err != nil { 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 { return nil, errors.New("not allowed to override in hilbish table") } @@ -213,7 +220,7 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { } var exitcode uint8 - stdout, stderr, err := execCommand(cmd, terminalOut) + stdout, stderr, err := execCommand(cmd, terminalOut, false) if code, ok := interp.IsExitStatus(err); ok { exitcode = code @@ -643,3 +650,16 @@ func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 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 +} diff --git a/exec.go b/exec.go index 726a986..458903d 100644 --- a/exec.go +++ b/exec.go @@ -27,6 +27,8 @@ import ( var errNotExec = errors.New("not executable") var errNotFound = errors.New("not found") var runnerMode rt.Value = rt.StringValue("hybrid") +var currentHandle *exec.Cmd +var currentCmd string type execError struct{ typ string @@ -233,7 +235,7 @@ func handleSh(cmdString string) (input string, exitCode uint8, cont bool, runErr } func execSh(cmdString string) (string, uint8, bool, error) { - _, _, err := execCommand(cmdString, true) + _, _, err := execCommand(cmdString, true, true) if err != nil { // If input is incomplete, start multiline prompting if syntax.IsIncomplete(err) { @@ -254,7 +256,7 @@ func execSh(cmdString string) (string, uint8, bool, error) { } // 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), "") if err != nil { return nil, nil, err @@ -277,16 +279,21 @@ func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) { var bg bool for _, stmt := range file.Stmts { bg = false + printer.Print(buf, stmt.Cmd) + + stmtStr := buf.String() + buf.Reset() + + if interactiveCall { + currentCmd = stmtStr + } + if stmt.Background { bg = true - printer.Print(buf, stmt.Cmd) - - stmtStr := buf.String() - buf.Reset() jobs.add(stmtStr, []string{}, "") } - interp.ExecHandler(execHandle(bg))(runner) + interp.ExecHandler(execHandle(bg, interactiveCall))(runner) err = runner.Run(context.TODO(), stmt) if err != nil { return stdout, stderr, err @@ -296,7 +303,7 @@ func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) { 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 { _, argstring := splitInput(strings.Join(args, " ")) // i dont really like this but it works @@ -410,6 +417,10 @@ func execHandle(bg bool) interp.ExecHandlerFunc { Stderr: hc.Stderr, } + if interactiveCall { + currentHandle = &cmd + } + var j *job if bg { j = jobs.getLatest() @@ -419,6 +430,8 @@ func execHandle(bg bool) interp.ExecHandlerFunc { err = cmd.Start() } + execDone := make(chan struct{}, 1) + var suspended bool if err == nil { if done := ctx.Done(); done != nil { go func() { @@ -440,12 +453,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) - if bg { + if bg && !suspended { j.exitCode = int(exit) j.finish() } diff --git a/job.go b/job.go index 1beba9c..c8efca3 100644 --- a/job.go +++ b/job.go @@ -268,6 +268,18 @@ func (j *jobHandler) add(cmd string, args []string, path string) *job { 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 { j.mu.RLock() defer j.mu.RUnlock() diff --git a/job_unix.go b/job_unix.go index 5029012..e32cd46 100644 --- a/job_unix.go +++ b/job_unix.go @@ -8,6 +8,7 @@ import ( "syscall" "golang.org/x/sys/unix" + rt "github.com/arnodel/golua/runtime" ) func (j *job) foreground() error { @@ -36,3 +37,14 @@ func (j *job) background() error { proc.Signal(syscall.SIGCONT) 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 +} diff --git a/job_windows.go b/job_windows.go index 140a5d1..f4ccf7d 100644 --- a/job_windows.go +++ b/job_windows.go @@ -13,3 +13,7 @@ func (j *job) foreground() error { func (j *job) background() error { return errors.New("not supported on windows") } + +func (j *job) suspend() error { + return errors.New("not supported on windows") +} diff --git a/signal_unix.go b/signal_unix.go index 2e6c885..0fa3f57 100644 --- a/signal_unix.go +++ b/signal_unix.go @@ -3,23 +3,32 @@ package main import ( + "fmt" "syscall" "os" "os/signal" + + rt "github.com/arnodel/golua/runtime" ) func handleSignals() { c := make(chan os.Signal) - signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN, syscall.SIGTSTP) - signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGWINCH, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGQUIT) + signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN) + signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGWINCH, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGQUIT, syscall.SIGTSTP) for s := range c { switch s { - case os.Interrupt: hooks.Emit("signal.sigint") - case syscall.SIGTERM: exit(0) - case syscall.SIGWINCH: hooks.Emit("signal.resize") - case syscall.SIGUSR1: hooks.Emit("signal.sigusr1") - case syscall.SIGUSR2: hooks.Emit("signal.sigusr2") + case os.Interrupt: hooks.Emit("signal.sigint") + case syscall.SIGTERM: exit(0) + case syscall.SIGWINCH: hooks.Emit("signal.resize") + case syscall.SIGUSR1: hooks.Emit("signal.sigusr1") + 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) + } } } }