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  }