diff --git a/cmd/micro/command.go b/cmd/micro/command.go index 39bc2f8f..041ae46f 100644 --- a/cmd/micro/command.go +++ b/cmd/micro/command.go @@ -4,92 +4,70 @@ import ( "bytes" "os" "os/exec" + "os/signal" "regexp" "strings" - - "github.com/gdamore/tcell" ) -// HandleShellCommand runs the shell command and outputs to DisplayBlock -func HandleShellCommand(input string, view *View) { +// RunShellCommand executes a shell command and returns the output/error +func RunShellCommand(input string) (string, error) { inputCmd := strings.Split(input, " ")[0] args := strings.Split(input, " ")[1:] - // Execute Command cmd := exec.Command(inputCmd, args...) outputBytes := &bytes.Buffer{} - - cmd.Stdout = outputBytes // send output to buffer + cmd.Stdout = outputBytes + cmd.Stderr = outputBytes cmd.Start() - cmd.Wait() // wait for command to finish + err := cmd.Wait() // wait for command to finish outstring := outputBytes.String() - totalLines := strings.Split(outstring, "\n") - - if len(totalLines) < 3 { - messenger.Message(outstring) - return - } - - if outstring != "" { - // Display nonblank output - DisplayBlock(outstring) - } + return outstring, err } -// DisplayBlock displays txt -// It blocks the main loop -func DisplayBlock(text string) { - topline := 0 - _, height := screen.Size() - screen.HideCursor() - totalLines := strings.Split(text, "\n") - for { - screen.Clear() +// HandleShellCommand runs the shell command and outputs to DisplayBlock +func HandleShellCommand(input string, view *View, openTerm bool) { + inputCmd := strings.Split(input, " ")[0] + if !openTerm { + messenger.Message("Running...") + go func() { + output, err := RunShellCommand(input) + totalLines := strings.Split(output, "\n") - lineEnd := topline + height - if lineEnd > len(totalLines) { - lineEnd = len(totalLines) - } - lines := totalLines[topline:lineEnd] - for y, line := range lines { - for x, ch := range line { - st := defStyle - screen.SetContent(x, y, ch, nil, st) - } - } - - screen.Show() - - event := screen.PollEvent() - switch e := event.(type) { - case *tcell.EventResize: - _, height = e.Size() - case *tcell.EventKey: - switch e.Key() { - case tcell.KeyPgUp: - if topline > height { - topline = topline - height + if len(totalLines) < 3 { + if err == nil { + messenger.Message(inputCmd, " exited without error") } else { - topline = 0 + messenger.Message(inputCmd, " exited with error: ", err, ": ", output) } - case tcell.KeyPgDn: - if topline < len(totalLines)-height { - topline = topline + height - } - case tcell.KeyUp: - if topline > 0 { - topline-- - } - case tcell.KeyDown: - if topline < len(totalLines)-height { - topline++ - } - case tcell.KeyCtrlQ, tcell.KeyCtrlW, tcell.KeyEscape, tcell.KeyCtrlC: - return - default: - return + } else { + messenger.Message(output) } - } + Redraw(view) + }() + } else { + screen.Fini() + + args := strings.Split(input, " ")[1:] + + cmd := exec.Command(inputCmd, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + for range c { + cmd.Process.Kill() + } + }() + + cmd.Start() + cmd.Wait() + + TermMessage("") + + InitScreen() } } @@ -98,7 +76,7 @@ func HandleCommand(input string, view *View) { inputCmd := strings.Split(input, " ")[0] args := strings.Split(input, " ")[1:] - commands := []string{"set", "quit", "save", "replace"} + commands := []string{"set", "quit", "save", "replace", "run"} i := 0 cmd := inputCmd @@ -116,6 +94,8 @@ func HandleCommand(input string, view *View) { switch inputCmd { case "set": SetOption(view, args) + case "run": + HandleShellCommand(strings.Join(args, " "), view, false) case "quit": if view.CanClose("Quit anyway? (yes, no, save) ") { screen.Fini() diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 56831112..4665220b 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -109,6 +109,57 @@ func InitConfigDir() { } } +// InitScreen creates and initializes the tcell screen +func InitScreen() { + // Should we enable true color? + truecolor := os.Getenv("MICRO_TRUECOLOR") == "1" + + // In order to enable true color, we have to set the TERM to `xterm-truecolor` when + // initializing tcell, but after that, we can set the TERM back to whatever it was + oldTerm := os.Getenv("TERM") + if truecolor { + os.Setenv("TERM", "xterm-truecolor") + } + + // Initilize tcell + var err error + screen, err = tcell.NewScreen() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + if err = screen.Init(); err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Now we can put the TERM back to what it was before + if truecolor { + os.Setenv("TERM", oldTerm) + } + + // Default style + defStyle = tcell.StyleDefault. + Foreground(tcell.ColorDefault). + Background(tcell.ColorDefault) + + // There may be another default style defined in the colorscheme + if style, ok := colorscheme["default"]; ok { + defStyle = style + } + + screen.SetStyle(defStyle) + screen.EnableMouse() +} + +// Redraw redraws the screen and the given view +func Redraw(view *View) { + screen.Clear() + view.Display() + messenger.Display() + screen.Show() +} + var flagVersion = flag.Bool("version", false, "Show version number") func main() { @@ -125,6 +176,7 @@ func main() { } encoding.Register() + tcell.SetEncodingFallback(tcell.EncodingFallbackASCII) // Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro) InitConfigDir() @@ -135,31 +187,7 @@ func main() { buf := NewBuffer(string(input), filename) - // Should we enable true color? - truecolor := os.Getenv("MICRO_TRUECOLOR") == "1" - - // In order to enable true color, we have to set the TERM to `xterm-truecolor` when - // initializing tcell, but after that, we can set the TERM back to whatever it was - oldTerm := os.Getenv("TERM") - if truecolor { - os.Setenv("TERM", "xterm-truecolor") - } - - // Initilize tcell - screen, err = tcell.NewScreen() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - if err = screen.Init(); err != nil { - fmt.Println(err) - os.Exit(1) - } - - // Now we can put the TERM back to what it was before - if truecolor { - os.Setenv("TERM", oldTerm) - } + InitScreen() // This is just so if we have an error, we can exit cleanly and not completely // mess up the terminal being worked in @@ -173,30 +201,12 @@ func main() { } }() - // Default style - defStyle = tcell.StyleDefault. - Foreground(tcell.ColorDefault). - Background(tcell.ColorDefault) - - // There may be another default style defined in the colorscheme - if style, ok := colorscheme["default"]; ok { - defStyle = style - } - - screen.SetStyle(defStyle) - screen.EnableMouse() - messenger = new(Messenger) view := NewView(buf) for { // Display everything - screen.Clear() - - view.Display() - messenger.Display() - - screen.Show() + Redraw(view) // Wait for the user's action event := screen.PollEvent() @@ -211,7 +221,7 @@ func main() { case tcell.KeyCtrlQ: // Make sure not to quit if there are unsaved changes if helpOpen { - view.buf = buf + view.OpenBuffer(buf) helpOpen = false } else { if view.CanClose("Quit anyway? (yes, no, save) ") { @@ -227,16 +237,16 @@ func main() { case tcell.KeyCtrlB: input, canceled := messenger.Prompt("$ ") if !canceled { - HandleShellCommand(input, view) + HandleShellCommand(input, view, true) } case tcell.KeyCtrlG: if !helpOpen { helpBuffer := NewBuffer(helpTxt, "") helpBuffer.name = "Help" helpOpen = true - view.buf = helpBuffer + view.OpenBuffer(helpBuffer) } else { - view.buf = buf + view.OpenBuffer(buf) helpOpen = false } } diff --git a/cmd/micro/view.go b/cmd/micro/view.go index db406760..5cf21e66 100644 --- a/cmd/micro/view.go +++ b/cmd/micro/view.go @@ -276,6 +276,29 @@ func (v *View) SelectAll() { v.cursor.y = 0 } +// OpenBuffer opens a new buffer in this view. +// This resets the topline, event handler and cursor. +func (v *View) OpenBuffer(buf *Buffer) { + v.buf = buf + v.topline = 0 + // Put the cursor at the first spot + v.cursor = Cursor{ + x: 0, + y: 0, + v: v, + } + v.cursor.ResetSelection() + + v.eh = NewEventHandler(v) + + v.matches = Match(v) + + // Set mouseReleased to true because we assume the mouse is not being pressed when + // the editor is opened + v.mouseReleased = true + v.lastClickTime = time.Time{} +} + // OpenFile opens a new file in the current view // It makes sure that the current buffer can be closed first (unsaved changes) func (v *View) OpenFile() {