mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-25 18:07:07 +09:00
Change project layout and use go.mod
This commit is contained in:
130
internal/shell/shell.go
Normal file
130
internal/shell/shell.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/pkg/shellwords"
|
||||
)
|
||||
|
||||
// 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
|
||||
screen.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
|
||||
// }
|
||||
// }
|
||||
131
internal/shell/terminal.go
Normal file
131
internal/shell/terminal.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/terminal"
|
||||
)
|
||||
|
||||
type TermType int
|
||||
|
||||
const (
|
||||
TTClose = iota // Should be closed
|
||||
TTRunning // Currently running a command
|
||||
TTDone // Finished running a command
|
||||
)
|
||||
|
||||
// A Terminal holds information for the terminal emulator
|
||||
type Terminal struct {
|
||||
State terminal.State
|
||||
Term *terminal.VT
|
||||
title string
|
||||
Status TermType
|
||||
Selection [2]buffer.Loc
|
||||
wait bool
|
||||
getOutput bool
|
||||
output *bytes.Buffer
|
||||
callback string
|
||||
}
|
||||
|
||||
// HasSelection returns whether this terminal has a valid selection
|
||||
func (t *Terminal) HasSelection() bool {
|
||||
return t.Selection[0] != t.Selection[1]
|
||||
}
|
||||
|
||||
func (t *Terminal) Name() string {
|
||||
return t.title
|
||||
}
|
||||
|
||||
// GetSelection returns the selected text
|
||||
func (t *Terminal) GetSelection(width int) string {
|
||||
start := t.Selection[0]
|
||||
end := t.Selection[1]
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
var ret string
|
||||
var l buffer.Loc
|
||||
for y := start.Y; y <= end.Y; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
l.X, l.Y = x, y
|
||||
if l.GreaterEqual(start) && l.LessThan(end) {
|
||||
c, _, _ := t.State.Cell(x, y)
|
||||
ret += string(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Start begins a new command in this terminal with a given view
|
||||
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool) error {
|
||||
if len(execCmd) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := exec.Command(execCmd[0], execCmd[1:]...)
|
||||
t.output = nil
|
||||
if getOutput {
|
||||
t.output = bytes.NewBuffer([]byte{})
|
||||
}
|
||||
Term, _, err := terminal.Start(&t.State, cmd, t.output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Term = Term
|
||||
t.getOutput = getOutput
|
||||
t.Status = TTRunning
|
||||
t.title = execCmd[0] + ":" + strconv.Itoa(cmd.Process.Pid)
|
||||
t.wait = wait
|
||||
|
||||
go func() {
|
||||
for {
|
||||
err := Term.Parse()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "[Press enter to close]")
|
||||
break
|
||||
}
|
||||
screen.Redraw()
|
||||
}
|
||||
t.Stop()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops execution of the terminal and sets the Status
|
||||
// to TTDone
|
||||
func (t *Terminal) Stop() {
|
||||
t.Term.File().Close()
|
||||
t.Term.Close()
|
||||
if t.wait {
|
||||
t.Status = TTDone
|
||||
} else {
|
||||
t.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Close sets the Status to TTClose indicating that the terminal
|
||||
// is done and should be closed
|
||||
func (t *Terminal) Close() {
|
||||
t.Status = TTClose
|
||||
// call the lua function that the user has given as a callback
|
||||
if t.getOutput {
|
||||
// TODO: plugin callback on Term emulator
|
||||
// _, err := Call(t.callback, t.output.String())
|
||||
// if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
// TermMessage(err)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// WriteString writes a given string to this terminal's pty
|
||||
func (t *Terminal) WriteString(str string) {
|
||||
t.Term.File().WriteString(str)
|
||||
}
|
||||
Reference in New Issue
Block a user