diff --git a/cmd/micro/action/actions.go b/cmd/micro/action/actions.go index dcb47695..3a6e74da 100644 --- a/cmd/micro/action/actions.go +++ b/cmd/micro/action/actions.go @@ -10,6 +10,7 @@ import ( "github.com/zyedidia/micro/cmd/micro/buffer" "github.com/zyedidia/micro/cmd/micro/config" "github.com/zyedidia/micro/cmd/micro/screen" + "github.com/zyedidia/micro/cmd/micro/shell" "github.com/zyedidia/micro/cmd/micro/util" "github.com/zyedidia/tcell" ) @@ -370,16 +371,6 @@ func (h *BufHandler) SelectToEnd() bool { return true } -// InsertSpace inserts a space -func (h *BufHandler) InsertSpace() bool { - if h.Cursor.HasSelection() { - h.Cursor.DeleteSelection() - h.Cursor.ResetSelection() - } - h.Buf.Insert(h.Cursor.Loc, " ") - return true -} - // InsertNewline inserts a newline plus possible some whitespace if autoindent is on func (h *BufHandler) InsertNewline() bool { if h.Buf.Type == buffer.BTInfo { @@ -984,6 +975,13 @@ func (h *BufHandler) ToggleKeyMenu() bool { // ShellMode opens a terminal to run a shell command func (h *BufHandler) ShellMode() bool { + InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) { + if !canceled { + // The true here is for openTerm to make the command interactive + shell.RunInteractiveShell(resp, true, false) + } + }) + return false } diff --git a/cmd/micro/action/actions_posix.go b/cmd/micro/action/actions_posix.go index 46eac8af..e91ea256 100644 --- a/cmd/micro/action/actions_posix.go +++ b/cmd/micro/action/actions_posix.go @@ -13,7 +13,7 @@ import ( // This only works on linux and has no default binding. // This code was adapted from the suspend code in nsf/godit func (*BufHandler) Suspend() bool { - screen.TempFini() + screenb := screen.TempFini() // suspend the process pid := syscall.Getpid() @@ -22,7 +22,7 @@ func (*BufHandler) Suspend() bool { util.TermMessage(err) } - screen.TempStart() + screen.TempStart(screenb) return false } diff --git a/cmd/micro/action/bufhandler.go b/cmd/micro/action/bufhandler.go index 3beab69c..4af5be99 100644 --- a/cmd/micro/action/bufhandler.go +++ b/cmd/micro/action/bufhandler.go @@ -261,7 +261,6 @@ var BufKeyActions = map[string]BufKeyAction{ "ParagraphPrevious": (*BufHandler).ParagraphPrevious, "ParagraphNext": (*BufHandler).ParagraphNext, "InsertNewline": (*BufHandler).InsertNewline, - "InsertSpace": (*BufHandler).InsertSpace, "Backspace": (*BufHandler).Backspace, "Delete": (*BufHandler).Delete, "InsertTab": (*BufHandler).InsertTab, @@ -370,7 +369,6 @@ var MultiActions = []string{ "ParagraphPrevious", "ParagraphNext", "InsertNewline", - "InsertSpace", "Backspace", "Delete", "InsertTab", diff --git a/cmd/micro/action/command.go b/cmd/micro/action/command.go index dc2dcf72..27b16729 100644 --- a/cmd/micro/action/command.go +++ b/cmd/micro/action/command.go @@ -4,6 +4,8 @@ import ( "os" "github.com/zyedidia/micro/cmd/micro/buffer" + "github.com/zyedidia/micro/cmd/micro/screen" + "github.com/zyedidia/micro/cmd/micro/shell" "github.com/zyedidia/micro/cmd/micro/shellwords" "github.com/zyedidia/micro/cmd/micro/util" ) @@ -260,6 +262,15 @@ func Bind(args []string) { // Run runs a shell command in the background func Run(args []string) { + runf, err := shell.RunBackgroundShell(shellwords.Join(args...)) + if err != nil { + InfoBar.Error(err) + } else { + go func() { + InfoBar.Message(runf()) + screen.Redraw() + }() + } } // Quit closes the main view diff --git a/cmd/micro/display/uiwindow.go b/cmd/micro/display/uiwindow.go index 50f94a3c..493bd5c9 100644 --- a/cmd/micro/display/uiwindow.go +++ b/cmd/micro/display/uiwindow.go @@ -19,11 +19,16 @@ func NewUIWindow(n *views.Node) *UIWindow { func (w *UIWindow) drawNode(n *views.Node) { cs := n.Children() + dividerStyle := config.DefStyle + if style, ok := config.Colorscheme["divider"]; ok { + dividerStyle = style + } + for i, c := range cs { if c.IsLeaf() && c.Kind == views.STVert { if i != len(cs)-1 { for h := 0; h < c.H; h++ { - screen.Screen.SetContent(c.X+c.W, c.Y+h, '|', nil, config.DefStyle.Reverse(true)) + screen.Screen.SetContent(c.X+c.W, c.Y+h, '|', nil, dividerStyle.Reverse(true)) } } } else { diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 1870911b..52507427 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -190,14 +190,17 @@ func main() { action.InitGlobals() // Here is the event loop which runs in a separate thread - // go func() { - // events = make(chan tcell.Event) - // for { - // screen.Lock() - // events <- screen.Screen.PollEvent() - // screen.Unlock() - // } - // }() + go func() { + events = make(chan tcell.Event) + for { + screen.Lock() + e := screen.Screen.PollEvent() + screen.Unlock() + if e != nil { + events <- e + } + } + }() for { // Display everything @@ -214,12 +217,10 @@ func main() { var event tcell.Event // Check for new events - screen.Lock() - event = screen.Screen.PollEvent() - screen.Unlock() - // select { - // case event = <-events: - // } + select { + case event = <-events: + case <-screen.DrawChan: + } if event != nil { if action.InfoBar.HasPrompt { diff --git a/cmd/micro/screen/screen.go b/cmd/micro/screen/screen.go index 72fc0d08..54ffe90d 100644 --- a/cmd/micro/screen/screen.go +++ b/cmd/micro/screen/screen.go @@ -18,6 +18,7 @@ import ( // same time too. var Screen tcell.Screen var lock sync.Mutex +var DrawChan chan bool func Lock() { lock.Lock() @@ -27,21 +28,24 @@ func Unlock() { lock.Unlock() } -var screenWasNil bool +func Redraw() { + DrawChan <- true +} // TempFini shuts the screen down temporarily -func TempFini() { - screenWasNil = Screen == nil +func TempFini() bool { + screenWasNil := Screen == nil if !screenWasNil { - Lock() Screen.Fini() + Lock() Screen = nil } + return screenWasNil } // TempStart restarts the screen after it was temporarily disabled -func TempStart() { +func TempStart(screenWasNil bool) { if !screenWasNil { Init() Unlock() @@ -50,6 +54,8 @@ func TempStart() { // Init creates and initializes the tcell screen func Init() { + DrawChan = make(chan bool) + // Should we enable true color? truecolor := os.Getenv("MICRO_TRUECOLOR") == "1" diff --git a/cmd/micro/shell/shell.go b/cmd/micro/shell/shell.go new file mode 100644 index 00000000..9c57d616 --- /dev/null +++ b/cmd/micro/shell/shell.go @@ -0,0 +1,131 @@ +package shell + +import ( + "bytes" + "fmt" + "io" + "os" + "os/exec" + "os/signal" + "strings" + + "github.com/zyedidia/micro/cmd/micro/screen" + "github.com/zyedidia/micro/cmd/micro/shellwords" + "github.com/zyedidia/micro/cmd/micro/util" +) + +// 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 +} + +// RunCommand executes a shell command and returns the output/error +func RunCommand(input string) (string, error) { + args, err := shellwords.Split(input) + if err != nil { + return "", err + } + inputCmd := args[0] + + return ExecCommand(inputCmd, args[1:]...) +} + +// RunBackgroundShell runs a shell command in the background +// It returns a function which will run the command and returns a string +// message result +func RunBackgroundShell(input string) (func() string, error) { + args, err := shellwords.Split(input) + if err != nil { + return nil, err + } + inputCmd := args[0] + return func() string { + output, err := RunCommand(input) + totalLines := strings.Split(output, "\n") + + str := output + if len(totalLines) < 3 { + if err == nil { + str = fmt.Sprint(inputCmd, " exited without error") + } else { + str = fmt.Sprint(inputCmd, " exited with error: ", err, ": ", output) + } + } + return str + }, nil +} + +// RunInteractiveShell runs a shellcommand interactively +func RunInteractiveShell(input string, wait bool, getOutput bool) (string, error) { + args, err := shellwords.Split(input) + if err != nil { + return "", err + } + inputCmd := args[0] + + // Shut down the screen because we're going to interact directly with the shell + screenb := screen.TempFini() + + 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 wait { + // This is just so we don't return right away and let the user press enter to return + util.TermMessage("") + } + + // Start the screen back up + screen.TempStart(screenb) + + return output, err +} + +// UserCommand runs the shell command +// The openTerm argument specifies whether a terminal should be opened (for viewing output +// or interacting with stdin) +// func UserCommand(input string, openTerm bool, waitToFinish bool) string { +// if !openTerm { +// RunBackgroundShell(input) +// return "" +// } else { +// output, _ := RunInteractiveShell(input, waitToFinish, false) +// return output +// } +// } diff --git a/cmd/micro/util/message.go b/cmd/micro/util/message.go index 2a1ddce1..efaf059d 100644 --- a/cmd/micro/util/message.go +++ b/cmd/micro/util/message.go @@ -16,7 +16,7 @@ import ( // This will write the message, and wait for the user // to press and key to continue func TermMessage(msg ...interface{}) { - screen.TempFini() + screenb := screen.TempFini() fmt.Println(msg...) fmt.Print("\nPress enter to continue") @@ -24,7 +24,7 @@ func TermMessage(msg ...interface{}) { reader := bufio.NewReader(os.Stdin) reader.ReadString('\n') - screen.TempStart() + screen.TempStart(screenb) } // TermError sends an error to the user in the terminal. Like TermMessage except formatted