mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-13 09:57:51 +08:00
172 lines
3.9 KiB
Go
172 lines
3.9 KiB
Go
package shell
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/xiaq/persistent/hashmap"
|
|
"src.elv.sh/pkg/cli"
|
|
"src.elv.sh/pkg/cli/term"
|
|
"src.elv.sh/pkg/diag"
|
|
"src.elv.sh/pkg/edit"
|
|
"src.elv.sh/pkg/eval"
|
|
"src.elv.sh/pkg/eval/vals"
|
|
"src.elv.sh/pkg/eval/vars"
|
|
"src.elv.sh/pkg/parse"
|
|
"src.elv.sh/pkg/prog"
|
|
"src.elv.sh/pkg/sys"
|
|
)
|
|
|
|
// InteractiveRescueShell determines whether a panic results in a rescue shell
|
|
// being launched. It should be set to false by interactive mode unit tests.
|
|
var interactiveRescueShell bool = true
|
|
|
|
// InteractConfig keeps configuration for the interactive mode.
|
|
type InteractConfig struct {
|
|
SpawnDaemon bool
|
|
Paths Paths
|
|
}
|
|
|
|
// Interactive mode panic handler.
|
|
func handlePanic() {
|
|
r := recover()
|
|
if r != nil {
|
|
println()
|
|
print(sys.DumpStack())
|
|
println()
|
|
fmt.Println(r)
|
|
println("\nExecing recovery shell /bin/sh")
|
|
syscall.Exec("/bin/sh", []string{"/bin/sh"}, os.Environ())
|
|
}
|
|
}
|
|
|
|
// Interact runs an interactive shell session.
|
|
func Interact(fds [3]*os.File, cfg *InteractConfig) {
|
|
if interactiveRescueShell {
|
|
defer handlePanic()
|
|
}
|
|
ev, cleanup := setupShell(fds, cfg.Paths, cfg.SpawnDaemon)
|
|
defer cleanup()
|
|
|
|
// Build Editor.
|
|
var ed editor
|
|
if sys.IsATTY(fds[0]) {
|
|
newed := edit.NewEditor(cli.StdTTY, ev, ev.DaemonClient())
|
|
ev.AddBuiltin(eval.NsBuilder{}.AddNs("edit", newed.Ns()).Ns())
|
|
ed = newed
|
|
} else {
|
|
ed = newMinEditor(fds[0], fds[2])
|
|
}
|
|
|
|
// Source rc.elv.
|
|
if cfg.Paths.Rc != "" {
|
|
err := sourceRC(fds, ev, cfg.Paths.Rc)
|
|
if err != nil {
|
|
diag.ShowError(fds[2], err)
|
|
}
|
|
}
|
|
|
|
term.Sanitize(fds[0], fds[2])
|
|
|
|
cooldown := time.Second
|
|
cmdNum := 0
|
|
|
|
for {
|
|
cmdNum++
|
|
|
|
line, err := ed.ReadCode()
|
|
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
fmt.Fprintln(fds[2], "Editor error:", err)
|
|
if _, isMinEditor := ed.(*minEditor); !isMinEditor {
|
|
fmt.Fprintln(fds[2], "Falling back to basic line editor")
|
|
ed = newMinEditor(fds[0], fds[2])
|
|
} else {
|
|
fmt.Fprintln(fds[2], "Don't know what to do, pid is", os.Getpid())
|
|
fmt.Fprintln(fds[2], "Restarting editor in", cooldown)
|
|
time.Sleep(cooldown)
|
|
if cooldown < time.Minute {
|
|
cooldown *= 2
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
// No error; reset cooldown.
|
|
cooldown = time.Second
|
|
|
|
err = evalInTTY(ev, fds,
|
|
parse.Source{Name: fmt.Sprintf("[tty %v]", cmdNum), Code: line})
|
|
term.Sanitize(fds[0], fds[2])
|
|
if err != nil {
|
|
diag.ShowError(fds[2], err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func sourceRC(fds [3]*os.File, ev *eval.Evaler, rcPath string) error {
|
|
absPath, err := filepath.Abs(rcPath)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot get full path of rc.elv: %v", err)
|
|
}
|
|
code, err := readFileUTF8(absPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
err = evalInTTY(ev, fds, parse.Source{Name: absPath, Code: code, IsFile: true})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
extraGlobal := extractExports(ev.Global(), fds[2])
|
|
if extraGlobal != nil {
|
|
ev.AddGlobal(extraGlobal)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
const exportsVarName = "-exports-"
|
|
|
|
// If the namespace contains a variable named exportsVarName, extract its values
|
|
// into a namespace.
|
|
func extractExports(ns *eval.Ns, stderr io.Writer) *eval.Ns {
|
|
value, ok := ns.Index(exportsVarName)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
if prog.DeprecationLevel >= 15 {
|
|
fmt.Fprintln(stderr,
|
|
"the $-exports- mechanism is deprecated; use edit:add-vars instead.")
|
|
}
|
|
exports, ok := value.(hashmap.Map)
|
|
if !ok {
|
|
fmt.Fprintf(stderr, "$%s is not map, ignored\n", exportsVarName)
|
|
return nil
|
|
}
|
|
nb := eval.NsBuilder{}
|
|
for it := exports.Iterator(); it.HasElem(); it.Next() {
|
|
k, v := it.Elem()
|
|
name, ok := k.(string)
|
|
if !ok {
|
|
fmt.Fprintf(stderr, "$%s[%s] is not string, ignored\n",
|
|
exportsVarName, vals.Repr(k, vals.NoPretty))
|
|
continue
|
|
}
|
|
if ns.HasName(name) {
|
|
fmt.Fprintf(stderr, "$%s already exists, ignored $%s[%s]\n",
|
|
name, exportsVarName, name)
|
|
continue
|
|
}
|
|
nb.Add(name, vars.FromInit(v))
|
|
}
|
|
return nb.Ns()
|
|
}
|