lf/app.go

260 lines
4.7 KiB
Go
Raw Normal View History

2016-08-13 12:49:04 +00:00
package main
import (
"bufio"
2016-08-13 12:49:04 +00:00
"fmt"
"io"
2016-08-13 12:49:04 +00:00
"log"
"os"
"strconv"
2016-08-13 12:49:04 +00:00
"strings"
)
2017-05-15 09:30:50 +00:00
type cmdItem struct {
2017-11-19 18:55:13 +00:00
prefix string
value string
2017-05-15 09:30:50 +00:00
}
2016-12-17 21:47:37 +00:00
type app struct {
2017-11-19 18:55:13 +00:00
ui *ui
nav *nav
quitChan chan bool
2018-04-03 19:22:58 +00:00
cmdIn io.WriteCloser
2017-11-19 18:55:13 +00:00
cmdHist []cmdItem
cmdHistInd int
}
2016-12-17 21:47:37 +00:00
func newApp() *app {
ui := newUI()
nav := newNav(ui.wins[0].h)
2016-10-29 23:20:35 +00:00
2016-12-17 21:47:37 +00:00
return &app{
2017-11-19 18:55:13 +00:00
ui: ui,
nav: nav,
quitChan: make(chan bool, 1),
}
2016-08-13 12:49:04 +00:00
}
2017-11-19 18:55:13 +00:00
func (app *app) readFile(path string) {
log.Printf("reading file: %s", path)
2016-08-13 12:49:04 +00:00
2017-11-19 18:55:13 +00:00
f, err := os.Open(path)
if err != nil {
app.ui.printf("opening file: %s", err)
return
}
defer f.Close()
2016-08-13 12:49:04 +00:00
2017-11-19 18:55:13 +00:00
p := newParser(f)
for p.parse() {
p.expr.eval(app, nil)
2016-08-13 12:49:04 +00:00
}
2017-11-19 18:55:13 +00:00
if p.err != nil {
app.ui.printf("%s", p.err)
}
2016-08-13 12:49:04 +00:00
}
2016-11-06 20:25:59 +00:00
// This is the main event loop of the application. There are two channels to
// read expressions from client and server. Reading and evaluation are done on
// separate goroutines.
2017-11-19 18:55:13 +00:00
func (app *app) loop() {
clientChan := app.ui.readExpr()
2017-11-19 18:55:13 +00:00
serverChan := readExpr()
2016-10-29 23:20:35 +00:00
2016-08-13 12:49:04 +00:00
for {
select {
2017-11-19 18:55:13 +00:00
case <-app.quitChan:
2016-08-13 12:49:04 +00:00
log.Print("bye!")
2016-08-14 12:45:24 +00:00
if gLastDirPath != "" {
f, err := os.Create(gLastDirPath)
if err != nil {
2016-08-17 19:09:34 +00:00
log.Printf("opening last dir file: %s", err)
2016-08-14 12:45:24 +00:00
}
defer f.Close()
dir := app.nav.currDir()
_, err = f.WriteString(dir.path)
if err != nil {
2016-08-17 19:09:34 +00:00
log.Printf("writing last dir file: %s", err)
2016-08-14 12:45:24 +00:00
}
}
2016-08-13 12:49:04 +00:00
return
case d := <-app.nav.dirChan:
prev, ok := app.nav.dirCache[d.path]
if ok {
d.find(prev.name(), app.nav.height)
}
app.nav.dirCache[d.path] = d
for i := range app.nav.dirs {
if app.nav.dirs[i].path == d.path {
app.nav.dirs[i] = d
}
}
app.nav.position()
curr, err := app.nav.currFile()
if err == nil {
if d.path == app.nav.currDir().path {
app.ui.loadFile(app.nav)
}
if d.path == curr.path {
app.ui.dirPrev = d
}
}
app.ui.draw(app.nav)
case r := <-app.nav.regChan:
app.nav.regCache[r.path] = r
curr, err := app.nav.currFile()
if err == nil {
if r.path == curr.path {
app.ui.regPrev = r
}
}
app.ui.draw(app.nav)
2016-10-29 23:20:35 +00:00
case e := <-clientChan:
for i := 0; i < e.count; i++ {
e.expr.eval(app, nil)
}
app.ui.draw(app.nav)
2016-10-29 23:20:35 +00:00
case e := <-serverChan:
e.eval(app, nil)
app.ui.draw(app.nav)
2016-08-13 12:49:04 +00:00
}
}
}
2016-12-17 21:47:37 +00:00
func (app *app) exportVars() {
2016-08-13 12:49:04 +00:00
var envFile string
if f, err := app.nav.currFile(); err == nil {
2017-11-19 18:55:13 +00:00
envFile = f.path
2016-08-13 12:49:04 +00:00
}
marks := app.nav.currMarks()
envFiles := strings.Join(marks, gOpts.filesep)
2016-08-13 12:49:04 +00:00
os.Setenv("f", envFile)
os.Setenv("fs", envFiles)
if len(marks) == 0 {
os.Setenv("fx", envFile)
} else {
os.Setenv("fx", envFiles)
}
2016-12-17 21:47:37 +00:00
os.Setenv("id", strconv.Itoa(gClientID))
2016-08-13 12:49:04 +00:00
}
2017-11-19 18:55:13 +00:00
func waitKey() error {
cmd := pauseCommand()
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("waiting key: %s", err)
}
return nil
}
2016-08-13 12:49:04 +00:00
// This function is used to run a command in shell. Following modes are used:
//
// Prefix Wait Async Stdin Stdout Stderr UI action
// $ No No Yes Yes Yes Pause and then resume
// % No No No Yes Yes Display output in statline
// ! Yes No Yes Yes Yes Pause and then resume
// & No Yes No No No Do nothing
func (app *app) runShell(s string, args []string, prefix string) {
2016-08-13 12:49:04 +00:00
app.exportVars()
cmd := shellCommand(s, args)
2016-08-13 12:49:04 +00:00
var out io.Reader
switch prefix {
case "$", "!":
2016-08-13 12:49:04 +00:00
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
app.ui.pause()
defer app.ui.resume()
defer app.nav.renew(app.ui.wins[0].h)
case "%":
2018-04-03 19:22:58 +00:00
stdin, err := cmd.StdinPipe()
if err != nil {
2018-04-03 19:22:58 +00:00
log.Printf("writing stdin: %s", err)
}
2018-04-03 19:22:58 +00:00
app.cmdIn = stdin
stdout, err := cmd.StdoutPipe()
if err != nil {
2018-04-03 19:22:58 +00:00
log.Printf("reading stdout: %s", err)
}
2018-04-03 19:22:58 +00:00
out = stdout
cmd.Stderr = cmd.Stdout
2016-08-13 12:49:04 +00:00
}
var err error
switch prefix {
case "$", "!":
2016-08-13 12:49:04 +00:00
err = cmd.Run()
case "%", "&":
err = cmd.Start()
2016-08-13 12:49:04 +00:00
}
if err != nil {
2017-11-19 18:55:13 +00:00
app.ui.printf("running shell: %s", err)
2016-08-13 12:49:04 +00:00
}
switch prefix {
case "!":
2016-08-13 12:49:04 +00:00
if err := waitKey(); err != nil {
app.ui.printf("waiting key: %s", err)
2016-08-13 12:49:04 +00:00
}
}
app.ui.loadFile(app.nav)
app.ui.loadFileInfo(app.nav)
switch prefix {
case "%":
2018-04-03 19:22:58 +00:00
go func() {
app.ui.msg = ""
app.ui.cmdPrefix = ">"
reader := bufio.NewReader(out)
var buf []byte
for {
b, err := reader.ReadByte()
if err == io.EOF {
break
}
buf = append(buf, b)
app.ui.msg = string(buf)
app.ui.draw(app.nav)
if b == '\n' {
buf = nil
}
}
if err := cmd.Wait(); err != nil {
log.Printf("running shell: %s", err)
}
app.nav.renew(app.ui.wins[0].h)
app.ui.cmdPrefix = ""
app.ui.draw(app.nav)
2018-04-03 19:22:58 +00:00
}()
}
2016-08-13 12:49:04 +00:00
}