mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-16 05:47:06 +09:00
Add shell command support
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
131
cmd/micro/shell/shell.go
Normal 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
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user