diff --git a/cmd/micro/command.go b/cmd/micro/command.go index 2607cdb8..ade999cd 100644 --- a/cmd/micro/command.go +++ b/cmd/micro/command.go @@ -1,11 +1,8 @@ package main import ( - "bytes" "fmt" "os" - "os/exec" - "os/signal" "path/filepath" "regexp" "runtime" @@ -714,105 +711,15 @@ func ReplaceAll(args []string) { func Term(args []string) { var err error if len(args) == 0 { - err = CurView().StartTerminal([]string{os.Getenv("SHELL"), "-i"}) + err = CurView().StartTerminal([]string{os.Getenv("SHELL"), "-i"}, true, false, "") } else { - err = CurView().StartTerminal(args) + err = CurView().StartTerminal(args, true, false, "") } if err != nil { messenger.Error(err) } } -// RunShellCommand executes a shell command and returns the output/error -func RunShellCommand(input string) (string, error) { - args, err := shellwords.Split(input) - if err != nil { - return "", err - } - inputCmd := args[0] - - cmd := exec.Command(inputCmd, args[1:]...) - outputBytes := &bytes.Buffer{} - cmd.Stdout = outputBytes - cmd.Stderr = outputBytes - cmd.Start() - err = cmd.Wait() // wait for command to finish - outstring := outputBytes.String() - return outstring, err -} - -// HandleShellCommand runs the shell command -// The openTerm argument specifies whether a terminal should be opened (for viewing output -// or interacting with stdin) -func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string { - args, err := shellwords.Split(input) - if err != nil { - return "" - } - inputCmd := args[0] - if !openTerm { - // Simply run the command in the background and notify the user when it's done - messenger.Message("Running...") - go func() { - output, err := RunShellCommand(input) - totalLines := strings.Split(output, "\n") - - if len(totalLines) < 3 { - if err == nil { - messenger.Message(inputCmd, " exited without error") - } else { - messenger.Message(inputCmd, " exited with error: ", err, ": ", output) - } - } else { - messenger.Message(output) - } - // We have to make sure to redraw - RedrawAll() - }() - } else { - // Shut down the screen because we're going to interact directly with the shell - screen.Fini() - screen = nil - - args := args[1:] - - // Set up everything for the command - var output string - cmd := exec.Command(inputCmd, args...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - // This is a trap for Ctrl-C so that it doesn't kill micro - // Instead we trap Ctrl-C to kill the program we're running - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - go func() { - for range c { - cmd.Process.Kill() - } - }() - - cmd.Start() - err := cmd.Wait() - - if err != nil { - output = err.Error() - } - - if waitToFinish { - // This is just so we don't return right away and let the user press enter to return - TermMessage("") - } - - // Start the screen back up - InitScreen() - - return output - } - return "" -} - // HandleCommand handles input from the user func HandleCommand(input string) { args, err := shellwords.Split(input) diff --git a/cmd/micro/shell.go b/cmd/micro/shell.go new file mode 100644 index 00000000..94ed9569 --- /dev/null +++ b/cmd/micro/shell.go @@ -0,0 +1,131 @@ +package main + +import ( + "bytes" + "io" + "os" + "os/exec" + "os/signal" + "strings" + + "github.com/zyedidia/micro/cmd/micro/shellwords" +) + +// ExecCommand executes a command using exec +// It returns any output/errors +func ExecCommand(name string, arg ...string) (string, error) { + var err error + cmd := exec.Command(name, arg...) + outputBytes := &bytes.Buffer{} + cmd.Stdout = outputBytes + cmd.Stderr = outputBytes + err = cmd.Start() + if err != nil { + return "", err + } + err = cmd.Wait() // wait for command to finish + outstring := outputBytes.String() + return outstring, err +} + +// RunShellCommand executes a shell command and returns the output/error +func RunShellCommand(input string) (string, error) { + args, err := shellwords.Split(input) + if err != nil { + return "", err + } + inputCmd := args[0] + + return ExecCommand(inputCmd, args[1:]...) +} + +func RunBackgroundShell(input string) { + args, err := shellwords.Split(input) + if err != nil { + messenger.Error(err) + return + } + inputCmd := args[0] + messenger.Message("Running...") + go func() { + output, err := RunShellCommand(input) + totalLines := strings.Split(output, "\n") + + if len(totalLines) < 3 { + if err == nil { + messenger.Message(inputCmd, " exited without error") + } else { + messenger.Message(inputCmd, " exited with error: ", err, ": ", output) + } + } else { + messenger.Message(output) + } + // We have to make sure to redraw + RedrawAll() + }() +} + +func RunInteractiveShell(input string, wait bool, getOutput bool) string { + args, err := shellwords.Split(input) + if err != nil { + return "" + } + inputCmd := args[0] + + // Shut down the screen because we're going to interact directly with the shell + screen.Fini() + screen = nil + + args = args[1:] + + // Set up everything for the command + outputBytes := &bytes.Buffer{} + cmd := exec.Command(inputCmd, args...) + cmd.Stdin = os.Stdin + if getOutput { + cmd.Stdout = io.MultiWriter(os.Stdout, outputBytes) + } else { + cmd.Stdout = os.Stdout + } + cmd.Stderr = os.Stderr + + // This is a trap for Ctrl-C so that it doesn't kill micro + // Instead we trap Ctrl-C to kill the program we're running + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + for range c { + cmd.Process.Kill() + } + }() + + cmd.Start() + err = cmd.Wait() + + output := outputBytes.String() + if err != nil { + output = err.Error() + } + + if wait { + // This is just so we don't return right away and let the user press enter to return + TermMessage("") + } + + // Start the screen back up + InitScreen() + + return output +} + +// HandleShellCommand runs the shell command +// The openTerm argument specifies whether a terminal should be opened (for viewing output +// or interacting with stdin) +func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string { + if !openTerm { + RunBackgroundShell(input) + return "" + } else { + return RunInteractiveShell(input, waitToFinish, false) + } +} diff --git a/cmd/micro/shell_posix.go b/cmd/micro/shell_posix.go new file mode 100644 index 00000000..6c19bf40 --- /dev/null +++ b/cmd/micro/shell_posix.go @@ -0,0 +1,18 @@ +// +build linux darwin dragonfly solaris openbsd netbsd freebsd + +package main + +import ( + "github.com/zyedidia/micro/cmd/micro/shellwords" +) + +const TermEmuSupported = true + +func RunTermEmulator(input string, wait bool, getOutput bool) error { + args, err := shellwords.Split(input) + if err != nil { + return err + } + err = CurView().StartTerminal(args, wait, false, "") + return err +} diff --git a/cmd/micro/shell_windows.go b/cmd/micro/shell_windows.go new file mode 100644 index 00000000..4ad4c415 --- /dev/null +++ b/cmd/micro/shell_windows.go @@ -0,0 +1,9 @@ +// +build plan9 nacl windows + +package main + +const TermEmuSupported = false + +func RunTermEmulator(input string, wait bool, getOutput bool) string { + return "Unsupported" +} diff --git a/cmd/micro/terminal.go b/cmd/micro/terminal.go index 06a24ce3..1defa61f 100644 --- a/cmd/micro/terminal.go +++ b/cmd/micro/terminal.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "strconv" + "strings" "github.com/zyedidia/clipboard" "github.com/zyedidia/tcell" @@ -27,6 +28,8 @@ type Terminal struct { status int selection [2]Loc wait bool + getOutput bool + callback string } // HasSelection returns whether this terminal has a valid selection @@ -159,6 +162,11 @@ func (t *Terminal) Stop() { // is ready for a new command to execute func (t *Terminal) Close() { t.status = VTIdle + // call the lua function that the user has given as a callback + _, err := Call(t.callback) + if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") { + TermMessage(err) + } } // WriteString writes a given string to this terminal's pty diff --git a/cmd/micro/view.go b/cmd/micro/view.go index fec32013..7969c274 100644 --- a/cmd/micro/view.go +++ b/cmd/micro/view.go @@ -165,8 +165,11 @@ func (v *View) ToggleStatusLine() { } // StartTerminal execs a command in this view -func (v *View) StartTerminal(execCmd []string) error { +func (v *View) StartTerminal(execCmd []string, wait bool, getOutput bool, luaCallback string) error { err := v.term.Start(execCmd, v) + v.term.wait = wait + v.term.getOutput = getOutput + v.term.callback = luaCallback if err == nil { v.term.Resize(v.Width, v.Height) v.Type = vtTerm