github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/shell/shell.go (about) 1 // Package shell is the entry point for the terminal interface of Elvish. 2 package shell 3 4 import ( 5 "bufio" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "os/signal" 11 "syscall" 12 "time" 13 "unicode/utf8" 14 15 "github.com/elves/elvish/daemon/api" 16 "github.com/elves/elvish/edit" 17 "github.com/elves/elvish/eval" 18 "github.com/elves/elvish/sys" 19 "github.com/elves/elvish/util" 20 ) 21 22 var logger = util.GetLogger("[shell] ") 23 24 // Shell keeps flags to the shell. 25 type Shell struct { 26 ev *eval.Evaler 27 daemon *api.Client 28 cmd bool 29 } 30 31 func NewShell(ev *eval.Evaler, daemon *api.Client, cmd bool) *Shell { 32 return &Shell{ev, daemon, cmd} 33 } 34 35 // Run runs Elvish using the default terminal interface. It blocks until Elvish 36 // quites, and returns the exit code. 37 func (sh *Shell) Run(args []string) int { 38 defer rescue() 39 40 handleUsr1AndQuit() 41 logSignals() 42 43 if len(args) > 0 { 44 if len(args) > 1 { 45 fmt.Fprintln(os.Stderr, "passing argument is not yet supported.") 46 return 2 47 } 48 arg := args[0] 49 if sh.cmd { 50 sourceTextAndPrintError(sh.ev, "code from -c", arg) 51 } else { 52 script(sh.ev, arg) 53 } 54 } else if !sys.IsATTY(0) { 55 script(sh.ev, "/dev/stdin") 56 } else { 57 interact(sh.ev, sh.daemon) 58 } 59 60 return 0 61 } 62 63 func rescue() { 64 r := recover() 65 if r != nil { 66 println() 67 fmt.Println(r) 68 print(sys.DumpStack()) 69 println("\nexecing recovery shell /bin/sh") 70 syscall.Exec("/bin/sh", []string{"/bin/sh"}, os.Environ()) 71 } 72 } 73 74 func script(ev *eval.Evaler, fname string) { 75 if !source(ev, fname, false) { 76 os.Exit(1) 77 } 78 } 79 80 func source(ev *eval.Evaler, fname string, notexistok bool) bool { 81 src, err := readFileUTF8(fname) 82 if err != nil { 83 if notexistok && os.IsNotExist(err) { 84 return true 85 } 86 fmt.Fprintln(os.Stderr, err) 87 return false 88 } 89 90 return sourceTextAndPrintError(ev, fname, src) 91 } 92 93 // sourceTextAndPrintError sources text, prints error if there is any, and 94 // returns whether there was no error. 95 func sourceTextAndPrintError(ev *eval.Evaler, name, src string) bool { 96 err := ev.SourceText(name, src) 97 if err != nil { 98 switch err := err.(type) { 99 case util.Pprinter: 100 fmt.Fprintln(os.Stderr, err.Pprint("")) 101 default: 102 fmt.Fprintf(os.Stderr, "\033[31;1m%s\033[m", err.Error()) 103 } 104 return false 105 } 106 return true 107 } 108 109 func readFileUTF8(fname string) (string, error) { 110 bytes, err := ioutil.ReadFile(fname) 111 if err != nil { 112 return "", err 113 } 114 if !utf8.Valid(bytes) { 115 return "", fmt.Errorf("%s: source is not valid UTF-8", fname) 116 } 117 return string(bytes), nil 118 } 119 120 func interact(ev *eval.Evaler, daemon *api.Client) { 121 // Build Editor. 122 sigch := make(chan os.Signal) 123 signal.Notify(sigch) 124 ed := edit.NewEditor(os.Stdin, os.Stderr, sigch, ev, daemon) 125 126 // Source rc.elv. 127 if ev.DataDir != "" { 128 source(ev, ev.DataDir+"/rc.elv", true) 129 } 130 131 // Build readLine function. 132 readLine := func() (string, error) { 133 return ed.ReadLine() 134 } 135 136 cooldown := time.Second 137 usingBasic := false 138 cmdNum := 0 139 140 for { 141 cmdNum++ 142 // name := fmt.Sprintf("<tty %d>", cmdNum) 143 144 line, err := readLine() 145 146 if err == io.EOF { 147 break 148 } else if err != nil { 149 fmt.Println("Editor error:", err) 150 if !usingBasic { 151 fmt.Println("Falling back to basic line editor") 152 readLine = basicReadLine 153 usingBasic = true 154 } else { 155 fmt.Println("Don't know what to do, pid is", os.Getpid()) 156 fmt.Println("Restarting editor in", cooldown) 157 time.Sleep(cooldown) 158 if cooldown < time.Minute { 159 cooldown *= 2 160 } 161 } 162 continue 163 } 164 165 // No error; reset cooldown. 166 cooldown = time.Second 167 168 sourceTextAndPrintError(ev, "[interactive]", line) 169 } 170 } 171 172 func basicReadLine() (string, error) { 173 stdin := bufio.NewReaderSize(os.Stdin, 0) 174 return stdin.ReadString('\n') 175 } 176 177 func logSignals() { 178 sigs := make(chan os.Signal) 179 signal.Notify(sigs) 180 go func() { 181 for sig := range sigs { 182 logger.Println("signal", sig) 183 } 184 }() 185 } 186 187 func handleUsr1AndQuit() { 188 sigs := make(chan os.Signal) 189 signal.Notify(sigs, syscall.SIGUSR1, syscall.SIGQUIT) 190 go func() { 191 for sig := range sigs { 192 fmt.Print(sys.DumpStack()) 193 if sig == syscall.SIGQUIT { 194 os.Exit(3) 195 } 196 } 197 }() 198 }