github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/shell/shell.go (about) 1 // Package shell is the entry point for the terminal interface of Elvish. 2 package shell 3 4 import ( 5 "fmt" 6 "io" 7 "os" 8 "os/signal" 9 "strconv" 10 "time" 11 12 "github.com/markusbkk/elvish/pkg/cli/term" 13 "github.com/markusbkk/elvish/pkg/daemon/daemondefs" 14 "github.com/markusbkk/elvish/pkg/env" 15 "github.com/markusbkk/elvish/pkg/eval" 16 "github.com/markusbkk/elvish/pkg/logutil" 17 "github.com/markusbkk/elvish/pkg/mods" 18 "github.com/markusbkk/elvish/pkg/parse" 19 "github.com/markusbkk/elvish/pkg/prog" 20 "github.com/markusbkk/elvish/pkg/sys" 21 ) 22 23 var logger = logutil.GetLogger("[shell] ") 24 25 // Program is the shell subprogram. 26 type Program struct { 27 ActivateDaemon daemondefs.ActivateFunc 28 29 codeInArg bool 30 compileOnly bool 31 noRC bool 32 rc string 33 json *bool 34 daemonPaths *prog.DaemonPaths 35 } 36 37 func (p *Program) RegisterFlags(fs *prog.FlagSet) { 38 // Support -i so that programs that expect shells to support it (like 39 // "script") don't error when they invoke Elvish. 40 fs.Bool("i", false, "force interactive mode; currently ignored") 41 fs.BoolVar(&p.codeInArg, "c", false, "take first argument as code to execute") 42 fs.BoolVar(&p.compileOnly, "compileonly", false, "Parse/Compile but do not execute") 43 fs.BoolVar(&p.noRC, "norc", false, "run elvish without invoking rc.elv") 44 fs.StringVar(&p.rc, "rc", "", "path to rc.elv") 45 46 p.json = fs.JSON() 47 if p.ActivateDaemon != nil { 48 p.daemonPaths = fs.DaemonPaths() 49 } 50 } 51 52 func (p *Program) Run(fds [3]*os.File, args []string) error { 53 cleanup1 := IncSHLVL() 54 defer cleanup1() 55 cleanup2 := initTTYAndSignal(fds[2]) 56 defer cleanup2() 57 58 ev := MakeEvaler(fds[2]) 59 60 if len(args) > 0 { 61 exit := script( 62 ev, fds, args, &scriptCfg{ 63 Cmd: p.codeInArg, CompileOnly: p.compileOnly, JSON: *p.json}) 64 return prog.Exit(exit) 65 } 66 67 var spawnCfg *daemondefs.SpawnConfig 68 if p.ActivateDaemon != nil { 69 var err error 70 spawnCfg, err = daemonPaths(p.daemonPaths) 71 if err != nil { 72 fmt.Fprintln(fds[2], "Warning:", err) 73 fmt.Fprintln(fds[2], "Storage daemon may not function.") 74 } 75 } 76 77 rc := "" 78 switch { 79 case p.noRC: 80 // Leave rc empty 81 case p.rc != "": 82 // Use explicit -rc flag value 83 rc = p.rc 84 default: 85 // Use default path to rc.elv 86 var err error 87 rc, err = rcPath() 88 if err != nil { 89 fmt.Fprintln(fds[2], "Warning:", err) 90 } 91 } 92 93 interact(ev, fds, &interactCfg{ 94 RC: rc, 95 ActivateDaemon: p.ActivateDaemon, SpawnConfig: spawnCfg}) 96 return nil 97 } 98 99 // MakeEvaler creates an Evaler, sets the module search directories and installs 100 // all the standard builtin modules. It writes a warning message to the supplied 101 // Writer if it could not initialize module search directories. 102 func MakeEvaler(stderr io.Writer) *eval.Evaler { 103 ev := eval.NewEvaler() 104 libs, err := libPaths() 105 if err != nil { 106 fmt.Fprintln(stderr, "Warning:", err) 107 } 108 ev.LibDirs = libs 109 mods.AddTo(ev) 110 return ev 111 } 112 113 // IncSHLVL increments the SHLVL environment variable. It returns a function to 114 // restore the original value of SHLVL. 115 func IncSHLVL() func() { 116 oldValue, hadValue := os.LookupEnv(env.SHLVL) 117 i, err := strconv.Atoi(oldValue) 118 if err != nil { 119 i = 0 120 } 121 os.Setenv(env.SHLVL, strconv.Itoa(i+1)) 122 123 if hadValue { 124 return func() { os.Setenv(env.SHLVL, oldValue) } 125 } else { 126 return func() { os.Unsetenv(env.SHLVL) } 127 } 128 } 129 130 func initTTYAndSignal(stderr io.Writer) func() { 131 restoreTTY := term.SetupGlobal() 132 133 sigCh := sys.NotifySignals() 134 go func() { 135 for sig := range sigCh { 136 logger.Println("signal", sig) 137 handleSignal(sig, stderr) 138 } 139 }() 140 141 return func() { 142 signal.Stop(sigCh) 143 restoreTTY() 144 } 145 } 146 147 func evalInTTY(ev *eval.Evaler, fds [3]*os.File, src parse.Source) (float64, error) { 148 start := time.Now() 149 ports, cleanup := eval.PortsFromFiles(fds, ev.ValuePrefix()) 150 defer cleanup() 151 err := ev.Eval(src, eval.EvalCfg{ 152 Ports: ports, Interrupt: eval.ListenInterrupts, PutInFg: true}) 153 end := time.Now() 154 return end.Sub(start).Seconds(), err 155 }