Add shell command support

This commit is contained in:
Zachary Yedidia
2019-01-10 16:37:05 -05:00
parent 0febfd2c80
commit c479c9d91a
9 changed files with 186 additions and 36 deletions

View File

@@ -10,6 +10,7 @@ import (
"github.com/zyedidia/micro/cmd/micro/buffer" "github.com/zyedidia/micro/cmd/micro/buffer"
"github.com/zyedidia/micro/cmd/micro/config" "github.com/zyedidia/micro/cmd/micro/config"
"github.com/zyedidia/micro/cmd/micro/screen" "github.com/zyedidia/micro/cmd/micro/screen"
"github.com/zyedidia/micro/cmd/micro/shell"
"github.com/zyedidia/micro/cmd/micro/util" "github.com/zyedidia/micro/cmd/micro/util"
"github.com/zyedidia/tcell" "github.com/zyedidia/tcell"
) )
@@ -370,16 +371,6 @@ func (h *BufHandler) SelectToEnd() bool {
return true 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 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
func (h *BufHandler) InsertNewline() bool { func (h *BufHandler) InsertNewline() bool {
if h.Buf.Type == buffer.BTInfo { if h.Buf.Type == buffer.BTInfo {
@@ -984,6 +975,13 @@ func (h *BufHandler) ToggleKeyMenu() bool {
// ShellMode opens a terminal to run a shell command // ShellMode opens a terminal to run a shell command
func (h *BufHandler) ShellMode() bool { 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 return false
} }

View File

@@ -13,7 +13,7 @@ import (
// This only works on linux and has no default binding. // This only works on linux and has no default binding.
// This code was adapted from the suspend code in nsf/godit // This code was adapted from the suspend code in nsf/godit
func (*BufHandler) Suspend() bool { func (*BufHandler) Suspend() bool {
screen.TempFini() screenb := screen.TempFini()
// suspend the process // suspend the process
pid := syscall.Getpid() pid := syscall.Getpid()
@@ -22,7 +22,7 @@ func (*BufHandler) Suspend() bool {
util.TermMessage(err) util.TermMessage(err)
} }
screen.TempStart() screen.TempStart(screenb)
return false return false
} }

View File

@@ -261,7 +261,6 @@ var BufKeyActions = map[string]BufKeyAction{
"ParagraphPrevious": (*BufHandler).ParagraphPrevious, "ParagraphPrevious": (*BufHandler).ParagraphPrevious,
"ParagraphNext": (*BufHandler).ParagraphNext, "ParagraphNext": (*BufHandler).ParagraphNext,
"InsertNewline": (*BufHandler).InsertNewline, "InsertNewline": (*BufHandler).InsertNewline,
"InsertSpace": (*BufHandler).InsertSpace,
"Backspace": (*BufHandler).Backspace, "Backspace": (*BufHandler).Backspace,
"Delete": (*BufHandler).Delete, "Delete": (*BufHandler).Delete,
"InsertTab": (*BufHandler).InsertTab, "InsertTab": (*BufHandler).InsertTab,
@@ -370,7 +369,6 @@ var MultiActions = []string{
"ParagraphPrevious", "ParagraphPrevious",
"ParagraphNext", "ParagraphNext",
"InsertNewline", "InsertNewline",
"InsertSpace",
"Backspace", "Backspace",
"Delete", "Delete",
"InsertTab", "InsertTab",

View File

@@ -4,6 +4,8 @@ import (
"os" "os"
"github.com/zyedidia/micro/cmd/micro/buffer" "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/shellwords"
"github.com/zyedidia/micro/cmd/micro/util" "github.com/zyedidia/micro/cmd/micro/util"
) )
@@ -260,6 +262,15 @@ func Bind(args []string) {
// Run runs a shell command in the background // Run runs a shell command in the background
func Run(args []string) { 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 // Quit closes the main view

View File

@@ -19,11 +19,16 @@ func NewUIWindow(n *views.Node) *UIWindow {
func (w *UIWindow) drawNode(n *views.Node) { func (w *UIWindow) drawNode(n *views.Node) {
cs := n.Children() cs := n.Children()
dividerStyle := config.DefStyle
if style, ok := config.Colorscheme["divider"]; ok {
dividerStyle = style
}
for i, c := range cs { for i, c := range cs {
if c.IsLeaf() && c.Kind == views.STVert { if c.IsLeaf() && c.Kind == views.STVert {
if i != len(cs)-1 { if i != len(cs)-1 {
for h := 0; h < c.H; h++ { 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 { } else {

View File

@@ -190,14 +190,17 @@ func main() {
action.InitGlobals() action.InitGlobals()
// Here is the event loop which runs in a separate thread // Here is the event loop which runs in a separate thread
// go func() { go func() {
// events = make(chan tcell.Event) events = make(chan tcell.Event)
// for { for {
// screen.Lock() screen.Lock()
// events <- screen.Screen.PollEvent() e := screen.Screen.PollEvent()
// screen.Unlock() screen.Unlock()
// } if e != nil {
// }() events <- e
}
}
}()
for { for {
// Display everything // Display everything
@@ -214,12 +217,10 @@ func main() {
var event tcell.Event var event tcell.Event
// Check for new events // Check for new events
screen.Lock() select {
event = screen.Screen.PollEvent() case event = <-events:
screen.Unlock() case <-screen.DrawChan:
// select { }
// case event = <-events:
// }
if event != nil { if event != nil {
if action.InfoBar.HasPrompt { if action.InfoBar.HasPrompt {

View File

@@ -18,6 +18,7 @@ import (
// same time too. // same time too.
var Screen tcell.Screen var Screen tcell.Screen
var lock sync.Mutex var lock sync.Mutex
var DrawChan chan bool
func Lock() { func Lock() {
lock.Lock() lock.Lock()
@@ -27,21 +28,24 @@ func Unlock() {
lock.Unlock() lock.Unlock()
} }
var screenWasNil bool func Redraw() {
DrawChan <- true
}
// TempFini shuts the screen down temporarily // TempFini shuts the screen down temporarily
func TempFini() { func TempFini() bool {
screenWasNil = Screen == nil screenWasNil := Screen == nil
if !screenWasNil { if !screenWasNil {
Lock()
Screen.Fini() Screen.Fini()
Lock()
Screen = nil Screen = nil
} }
return screenWasNil
} }
// TempStart restarts the screen after it was temporarily disabled // TempStart restarts the screen after it was temporarily disabled
func TempStart() { func TempStart(screenWasNil bool) {
if !screenWasNil { if !screenWasNil {
Init() Init()
Unlock() Unlock()
@@ -50,6 +54,8 @@ func TempStart() {
// Init creates and initializes the tcell screen // Init creates and initializes the tcell screen
func Init() { func Init() {
DrawChan = make(chan bool)
// Should we enable true color? // Should we enable true color?
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1" truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"

131
cmd/micro/shell/shell.go Normal file
View File

@@ -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
// }
// }

View File

@@ -16,7 +16,7 @@ import (
// This will write the message, and wait for the user // This will write the message, and wait for the user
// to press and key to continue // to press and key to continue
func TermMessage(msg ...interface{}) { func TermMessage(msg ...interface{}) {
screen.TempFini() screenb := screen.TempFini()
fmt.Println(msg...) fmt.Println(msg...)
fmt.Print("\nPress enter to continue") fmt.Print("\nPress enter to continue")
@@ -24,7 +24,7 @@ func TermMessage(msg ...interface{}) {
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
reader.ReadString('\n') reader.ReadString('\n')
screen.TempStart() screen.TempStart(screenb)
} }
// TermError sends an error to the user in the terminal. Like TermMessage except formatted // TermError sends an error to the user in the terminal. Like TermMessage except formatted