From 838a932dd96a709c37fbee7c0117ad4b29356d28 Mon Sep 17 00:00:00 2001 From: aerth Date: Tue, 19 Apr 2016 19:49:43 +0000 Subject: [PATCH 1/4] stdout and stderr buffers for command execution --- cmd/micro/command.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/cmd/micro/command.go b/cmd/micro/command.go index 39bc2f8f..eb610e88 100644 --- a/cmd/micro/command.go +++ b/cmd/micro/command.go @@ -14,15 +14,27 @@ import ( func HandleShellCommand(input string, view *View) { inputCmd := strings.Split(input, " ")[0] args := strings.Split(input, " ")[1:] - - // Execute Command + if inputCmd == "exit" { + messenger.Message("Ctrl+Q to exit") + return + } + if inputCmd == "help" { + DisplayHelp() + return + } cmd := exec.Command(inputCmd, args...) - outputBytes := &bytes.Buffer{} + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout // send output to buffer + cmd.Stderr = &stderr // send error to a different buffer + // Execute Command + err := cmd.Run() + if err != nil { + messenger.Message(err.Error() + "... " + stderr.String()) + return + } - cmd.Stdout = outputBytes // send output to buffer - cmd.Start() - cmd.Wait() // wait for command to finish - outstring := outputBytes.String() + outstring := stdout.String() totalLines := strings.Split(outstring, "\n") if len(totalLines) < 3 { @@ -31,7 +43,6 @@ func HandleShellCommand(input string, view *View) { } if outstring != "" { - // Display nonblank output DisplayBlock(outstring) } } From c767b3dc0ca73b5d7f6e6380b8b05a93a2a28ae2 Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Tue, 19 Apr 2016 18:58:52 -0400 Subject: [PATCH 2/4] Add OpenBuffer() function to View Fixes #43 --- cmd/micro/micro.go | 6 +++--- cmd/micro/view.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 56831112..ecc9d288 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -211,7 +211,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) ") { @@ -234,9 +234,9 @@ func main() { 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 69a65a75..f390efd8 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() { From 65745a6b4310f8d51910d8220c9cfa94797b8b9e Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Tue, 19 Apr 2016 21:25:13 -0400 Subject: [PATCH 3/4] Much improved terminal mode (Ctrl-b) This commit splits "Boss mode" into to different actions. Executing a command with Ctrl-b will now open an interactive terminal. If you would simply like to execute a background job in the shell, press Ctrl-e and then type `run shell-command`. --- cmd/micro/command.go | 116 ++++++++++++++++++------------------------- cmd/micro/micro.go | 99 +++++++++++++++++++----------------- 2 files changed, 102 insertions(+), 113 deletions(-) diff --git a/cmd/micro/command.go b/cmd/micro/command.go index 39bc2f8f..028493c9 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.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) } - 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 ecc9d288..01885c38 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() { @@ -135,31 +186,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 +200,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() @@ -227,7 +236,7 @@ func main() { case tcell.KeyCtrlB: input, canceled := messenger.Prompt("$ ") if !canceled { - HandleShellCommand(input, view) + HandleShellCommand(input, view, true) } case tcell.KeyCtrlG: if !helpOpen { From 4f6a6866d11d74e3753b2f412c03cbe15c3b3e36 Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Wed, 20 Apr 2016 07:23:16 -0400 Subject: [PATCH 4/4] Set encoding fallback to ASCII See #45 --- cmd/micro/micro.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 01885c38..4665220b 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -176,6 +176,7 @@ func main() { } encoding.Register() + tcell.SetEncodingFallback(tcell.EncodingFallbackASCII) // Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro) InitConfigDir()