lf/eval.go
SeerLite 8adbd2b9ef
Don't use the absolute path when saving bookmarks (#659)
The absolute path of symlinks were getting saved, so bookmarking a symlink
directory would cause the absolute path to be saved and loaded instead.
What's saved now is the current path string as understood by lf instead, so
path to symlinks can be saved too and they won't get dereferenced when
loading.
2021-07-09 16:34:45 +03:00

1713 lines
41 KiB
Go

package main
import (
"io"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"
)
func (e *setExpr) eval(app *app, args []string) {
switch e.opt {
case "anchorfind":
gOpts.anchorfind = true
case "noanchorfind":
gOpts.anchorfind = false
case "anchorfind!":
gOpts.anchorfind = !gOpts.anchorfind
case "autoquit":
gOpts.autoquit = true
case "noautoquit":
gOpts.autoquit = false
case "autoquit!":
gOpts.autoquit = !gOpts.autoquit
case "dircounts":
gOpts.dircounts = true
case "nodircounts":
gOpts.dircounts = false
case "dircounts!":
gOpts.dircounts = !gOpts.dircounts
case "dirfirst":
gOpts.sortType.option |= dirfirstSort
app.nav.sort()
app.ui.sort()
case "nodirfirst":
gOpts.sortType.option &= ^dirfirstSort
app.nav.sort()
app.ui.sort()
case "dirfirst!":
gOpts.sortType.option ^= dirfirstSort
app.nav.sort()
app.ui.sort()
case "drawbox":
gOpts.drawbox = true
app.ui.renew()
if app.nav.height != app.ui.wins[0].h {
app.nav.height = app.ui.wins[0].h
app.nav.regCache = make(map[string]*reg)
}
app.ui.loadFile(app.nav, true)
case "nodrawbox":
gOpts.drawbox = false
app.ui.renew()
if app.nav.height != app.ui.wins[0].h {
app.nav.height = app.ui.wins[0].h
app.nav.regCache = make(map[string]*reg)
}
app.ui.loadFile(app.nav, true)
case "drawbox!":
gOpts.drawbox = !gOpts.drawbox
app.ui.renew()
if app.nav.height != app.ui.wins[0].h {
app.nav.height = app.ui.wins[0].h
app.nav.regCache = make(map[string]*reg)
}
app.ui.loadFile(app.nav, true)
case "globsearch":
gOpts.globsearch = true
case "noglobsearch":
gOpts.globsearch = false
case "globsearch!":
gOpts.globsearch = !gOpts.globsearch
case "hidden":
gOpts.sortType.option |= hiddenSort
app.nav.sort()
app.nav.position()
app.ui.sort()
app.ui.loadFile(app.nav, true)
case "nohidden":
gOpts.sortType.option &= ^hiddenSort
app.nav.sort()
app.nav.position()
app.ui.sort()
app.ui.loadFile(app.nav, true)
case "hidden!":
gOpts.sortType.option ^= hiddenSort
app.nav.sort()
app.nav.position()
app.ui.sort()
app.ui.loadFile(app.nav, true)
case "icons":
gOpts.icons = true
case "noicons":
gOpts.icons = false
case "icons!":
gOpts.icons = !gOpts.icons
case "ignorecase":
gOpts.ignorecase = true
app.nav.sort()
app.ui.sort()
case "noignorecase":
gOpts.ignorecase = false
app.nav.sort()
app.ui.sort()
case "ignorecase!":
gOpts.ignorecase = !gOpts.ignorecase
app.nav.sort()
app.ui.sort()
case "ignoredia":
gOpts.ignoredia = true
app.nav.sort()
app.ui.sort()
case "noignoredia":
gOpts.ignoredia = false
app.nav.sort()
app.ui.sort()
case "ignoredia!":
gOpts.ignoredia = !gOpts.ignoredia
app.nav.sort()
app.ui.sort()
case "incsearch":
gOpts.incsearch = true
case "noincsearch":
gOpts.incsearch = false
case "incsearch!":
gOpts.incsearch = !gOpts.incsearch
case "mouse":
if !gOpts.mouse {
gOpts.mouse = true
app.ui.screen.EnableMouse()
}
case "nomouse":
if gOpts.mouse {
gOpts.mouse = false
app.ui.screen.DisableMouse()
}
case "mouse!":
if gOpts.mouse {
gOpts.mouse = false
app.ui.screen.DisableMouse()
} else {
gOpts.mouse = true
app.ui.screen.EnableMouse()
}
case "number":
gOpts.number = true
case "nonumber":
gOpts.number = false
case "number!":
gOpts.number = !gOpts.number
case "preview":
if len(gOpts.ratios) < 2 {
app.ui.echoerr("preview: 'ratios' should consist of at least two numbers before enabling 'preview'")
return
}
gOpts.preview = true
case "nopreview":
gOpts.preview = false
case "preview!":
if len(gOpts.ratios) < 2 {
app.ui.echoerr("preview: 'ratios' should consist of at least two numbers before enabling 'preview'")
return
}
gOpts.preview = !gOpts.preview
case "relativenumber":
gOpts.relativenumber = true
case "norelativenumber":
gOpts.relativenumber = false
case "relativenumber!":
gOpts.relativenumber = !gOpts.relativenumber
case "reverse":
gOpts.sortType.option |= reverseSort
app.nav.sort()
app.ui.sort()
case "noreverse":
gOpts.sortType.option &= ^reverseSort
app.nav.sort()
app.ui.sort()
case "reverse!":
gOpts.sortType.option ^= reverseSort
app.nav.sort()
app.ui.sort()
case "smartcase":
gOpts.smartcase = true
case "nosmartcase":
gOpts.smartcase = false
case "smartcase!":
gOpts.smartcase = !gOpts.smartcase
case "smartdia":
gOpts.smartdia = true
case "nosmartdia":
gOpts.smartdia = false
case "smartdia!":
gOpts.smartdia = !gOpts.smartdia
case "waitmsg":
gOpts.waitmsg = e.val
case "wrapscan":
gOpts.wrapscan = true
case "nowrapscan":
gOpts.wrapscan = false
case "wrapscan!":
gOpts.wrapscan = !gOpts.wrapscan
case "wrapscroll":
gOpts.wrapscroll = true
case "nowrapscroll":
gOpts.wrapscroll = false
case "wrapscroll!":
gOpts.wrapscroll = !gOpts.wrapscroll
case "findlen":
n, err := strconv.Atoi(e.val)
if err != nil {
app.ui.echoerrf("findlen: %s", err)
return
}
if n < 0 {
app.ui.echoerr("findlen: value should be a non-negative number")
return
}
gOpts.findlen = n
case "period":
n, err := strconv.Atoi(e.val)
if err != nil {
app.ui.echoerrf("period: %s", err)
return
}
if n < 0 {
app.ui.echoerr("period: value should be a non-negative number")
return
}
gOpts.period = n
if n == 0 {
app.ticker.Stop()
} else {
app.ticker.Stop()
app.ticker = time.NewTicker(time.Duration(gOpts.period) * time.Second)
}
case "scrolloff":
n, err := strconv.Atoi(e.val)
if err != nil {
app.ui.echoerrf("scrolloff: %s", err)
return
}
if n < 0 {
app.ui.echoerr("scrolloff: value should be a non-negative number")
return
}
gOpts.scrolloff = n
case "tabstop":
n, err := strconv.Atoi(e.val)
if err != nil {
app.ui.echoerrf("tabstop: %s", err)
return
}
if n <= 0 {
app.ui.echoerr("tabstop: value should be a positive number")
return
}
gOpts.tabstop = n
case "errorfmt":
gOpts.errorfmt = e.val
case "filesep":
gOpts.filesep = e.val
case "hiddenfiles":
toks := strings.Split(e.val, ":")
for _, s := range toks {
if s == "" {
app.ui.echoerr("hiddenfiles: glob should be non-empty")
return
}
_, err := filepath.Match(s, "a")
if err != nil {
app.ui.echoerrf("hiddenfiles: %s", err)
return
}
}
gOpts.hiddenfiles = toks
app.nav.sort()
app.nav.position()
app.ui.sort()
app.ui.loadFile(app.nav, true)
case "ifs":
gOpts.ifs = e.val
case "info":
if e.val == "" {
gOpts.info = nil
return
}
toks := strings.Split(e.val, ":")
for _, s := range toks {
switch s {
case "size", "time", "atime", "ctime":
default:
app.ui.echoerr("info: should consist of 'size', 'time', 'atime' or 'ctime' separated with colon")
return
}
}
gOpts.info = toks
case "previewer":
gOpts.previewer = replaceTilde(e.val)
case "cleaner":
gOpts.cleaner = replaceTilde(e.val)
case "promptfmt":
gOpts.promptfmt = e.val
case "ratios":
toks := strings.Split(e.val, ":")
var rats []int
for _, s := range toks {
n, err := strconv.Atoi(s)
if err != nil {
app.ui.echoerrf("ratios: %s", err)
return
}
if n <= 0 {
app.ui.echoerr("ratios: value should be a positive number")
return
}
rats = append(rats, n)
}
if gOpts.preview && len(rats) < 2 {
app.ui.echoerr("ratios: should consist of at least two numbers when 'preview' is enabled")
return
}
gOpts.ratios = rats
app.ui.wins = getWins(app.ui.screen)
app.ui.loadFile(app.nav, true)
case "shell":
gOpts.shell = e.val
case "shellflag":
gOpts.shellflag = e.val
case "shellopts":
if e.val == "" {
gOpts.shellopts = nil
return
}
gOpts.shellopts = strings.Split(e.val, ":")
case "sortby":
switch e.val {
case "natural":
gOpts.sortType.method = naturalSort
case "name":
gOpts.sortType.method = nameSort
case "size":
gOpts.sortType.method = sizeSort
case "time":
gOpts.sortType.method = timeSort
case "ctime":
gOpts.sortType.method = ctimeSort
case "atime":
gOpts.sortType.method = atimeSort
case "ext":
gOpts.sortType.method = extSort
default:
app.ui.echoerr("sortby: value should either be 'natural', 'name', 'size', 'time', 'atime', 'ctime' or 'ext'")
return
}
app.nav.sort()
app.ui.sort()
case "timefmt":
gOpts.timefmt = e.val
case "truncatechar":
if runeSliceWidth([]rune(e.val)) != 1 {
app.ui.echoerr("truncatechar: value should be a single character")
return
}
gOpts.truncatechar = e.val
default:
app.ui.echoerrf("unknown option: %s", e.opt)
return
}
app.ui.loadFileInfo(app.nav)
}
func (e *mapExpr) eval(app *app, args []string) {
if e.expr == nil {
delete(gOpts.keys, e.keys)
} else {
gOpts.keys[e.keys] = e.expr
}
app.ui.loadFileInfo(app.nav)
}
func (e *cmapExpr) eval(app *app, args []string) {
if e.cmd == "" {
delete(gOpts.cmdkeys, e.key)
} else {
gOpts.cmdkeys[e.key] = &callExpr{e.cmd, nil, 1}
}
app.ui.loadFileInfo(app.nav)
}
func (e *cmdExpr) eval(app *app, args []string) {
if e.expr == nil {
delete(gOpts.cmds, e.name)
} else {
gOpts.cmds[e.name] = e.expr
}
app.ui.loadFileInfo(app.nav)
}
func onChdir(app *app) {
if cmd, ok := gOpts.cmds["on-cd"]; ok {
cmd.eval(app, nil)
}
}
func splitKeys(s string) (keys []string) {
for i := 0; i < len(s); {
r, w := utf8.DecodeRuneInString(s[i:])
if r != '<' {
keys = append(keys, s[i:i+w])
i += w
} else {
j := i + w
for r != '>' && j < len(s) {
r, w = utf8.DecodeRuneInString(s[j:])
j += w
}
keys = append(keys, s[i:j])
i = j
}
}
return
}
func update(app *app) {
// Remove the current menu buffer
app.ui.menuBuf = nil
app.ui.menuSelected = -2
switch {
case gOpts.incsearch && app.ui.cmdPrefix == "/":
app.nav.search = string(app.ui.cmdAccLeft) + string(app.ui.cmdAccRight)
dir := app.nav.currDir()
old := dir.ind
dir.ind = app.nav.searchInd
dir.pos = app.nav.searchPos
if _, err := app.nav.searchNext(); err != nil {
app.ui.echoerrf("search: %s: %s", err, app.nav.search)
} else if old != dir.ind {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case gOpts.incsearch && app.ui.cmdPrefix == "?":
app.nav.search = string(app.ui.cmdAccLeft) + string(app.ui.cmdAccRight)
dir := app.nav.currDir()
old := dir.ind
dir.ind = app.nav.searchInd
dir.pos = app.nav.searchPos
if _, err := app.nav.searchPrev(); err != nil {
app.ui.echoerrf("search: %s: %s", err, app.nav.search)
} else if old != dir.ind {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
}
}
func normal(app *app) {
app.ui.menuBuf = nil
app.ui.menuSelected = -2
app.ui.cmdAccLeft = nil
app.ui.cmdAccRight = nil
app.ui.cmdTmp = nil
app.ui.cmdPrefix = ""
}
func insert(app *app, arg string) {
switch {
case gOpts.incsearch && (app.ui.cmdPrefix == "/" || app.ui.cmdPrefix == "?"):
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(arg)...)
update(app)
case app.ui.cmdPrefix == "find: ":
app.nav.find = string(app.ui.cmdAccLeft) + arg + string(app.ui.cmdAccRight)
if gOpts.findlen == 0 {
switch app.nav.findSingle() {
case 0:
app.ui.echoerrf("find: pattern not found: %s", app.nav.find)
case 1:
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
default:
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(arg)...)
return
}
} else {
if len(app.nav.find) < gOpts.findlen {
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(arg)...)
return
}
if moved, found := app.nav.findNext(); !found {
app.ui.echoerrf("find: pattern not found: %s", app.nav.find)
} else if moved {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
}
normal(app)
case app.ui.cmdPrefix == "find-back: ":
app.nav.find = string(app.ui.cmdAccLeft) + arg + string(app.ui.cmdAccRight)
if gOpts.findlen == 0 {
switch app.nav.findSingle() {
case 0:
app.ui.echoerrf("find-back: pattern not found: %s", app.nav.find)
case 1:
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
default:
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(arg)...)
return
}
} else {
if len(app.nav.find) < gOpts.findlen {
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(arg)...)
return
}
if moved, found := app.nav.findPrev(); !found {
app.ui.echoerrf("find-back: pattern not found: %s", app.nav.find)
} else if moved {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
}
normal(app)
case strings.HasPrefix(app.ui.cmdPrefix, "delete"):
normal(app)
if arg == "y" {
if err := app.nav.del(app.ui); err != nil {
app.ui.echoerrf("delete: %s", err)
return
}
app.nav.unselect()
if gSingleMode {
app.nav.renew()
app.ui.loadFile(app.nav, true)
} else {
if err := remote("send load"); err != nil {
app.ui.echoerrf("delete: %s", err)
return
}
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case strings.HasPrefix(app.ui.cmdPrefix, "replace"):
normal(app)
if arg == "y" {
if err := app.nav.rename(); err != nil {
app.ui.echoerrf("rename: %s", err)
return
}
if gSingleMode {
app.nav.renew()
app.ui.loadFile(app.nav, true)
} else {
if err := remote("send load"); err != nil {
app.ui.echoerrf("rename: %s", err)
return
}
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case strings.HasPrefix(app.ui.cmdPrefix, "create"):
normal(app)
if arg == "y" {
if err := os.MkdirAll(filepath.Dir(app.nav.renameNewPath), os.ModePerm); err != nil {
app.ui.echoerrf("rename: %s", err)
return
}
if err := app.nav.rename(); err != nil {
app.ui.echoerrf("rename: %s", err)
return
}
if gSingleMode {
app.nav.renew()
app.ui.loadFile(app.nav, true)
} else {
if err := remote("send load"); err != nil {
app.ui.echoerrf("rename: %s", err)
return
}
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case app.ui.cmdPrefix == "mark-save: ":
normal(app)
app.nav.marks[arg] = app.nav.currDir().path
if err := app.nav.writeMarks(); err != nil {
app.ui.echoerrf("mark-save: %s", err)
}
if gSingleMode {
if err := app.nav.sync(); err != nil {
app.ui.echoerrf("mark-save: %s", err)
}
} else {
if err := remote("send sync"); err != nil {
app.ui.echoerrf("mark-save: %s", err)
}
}
case app.ui.cmdPrefix == "mark-load: ":
normal(app)
wd, err := os.Getwd()
if err != nil {
log.Printf("getting current directory: %s", err)
}
path, ok := app.nav.marks[arg]
if !ok {
app.ui.echoerr("mark-load: no such mark")
return
}
if err := app.nav.cd(path); err != nil {
app.ui.echoerrf("%s", err)
return
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
if wd != path {
app.nav.marks["'"] = wd
onChdir(app)
}
case app.ui.cmdPrefix == "mark-remove: ":
normal(app)
if err := app.nav.removeMark(arg); err != nil {
app.ui.echoerrf("mark-remove: %s", err)
return
}
if err := app.nav.writeMarks(); err != nil {
app.ui.echoerrf("mark-remove: %s", err)
return
}
if gSingleMode {
if err := app.nav.sync(); err != nil {
app.ui.echoerrf("mark-remove: %s", err)
}
} else {
if err := remote("send sync"); err != nil {
app.ui.echoerrf("mark-remove: %s", err)
}
}
default:
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(arg)...)
}
}
func (e *callExpr) eval(app *app, args []string) {
switch e.name {
case "up":
if app.ui.cmdPrefix != "" && app.ui.cmdPrefix != ">" {
normal(app)
}
if app.nav.up(e.count) {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "half-up":
if app.ui.cmdPrefix != "" && app.ui.cmdPrefix != ">" {
normal(app)
}
if app.nav.up(e.count * app.nav.height / 2) {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "page-up":
if app.ui.cmdPrefix != "" && app.ui.cmdPrefix != ">" {
normal(app)
}
if app.nav.up(e.count * app.nav.height) {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "down":
if app.ui.cmdPrefix != "" && app.ui.cmdPrefix != ">" {
normal(app)
}
if app.nav.down(e.count) {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "half-down":
if app.ui.cmdPrefix != "" && app.ui.cmdPrefix != ">" {
normal(app)
}
if app.nav.down(e.count * app.nav.height / 2) {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "page-down":
if app.ui.cmdPrefix != "" && app.ui.cmdPrefix != ">" {
normal(app)
}
if app.nav.down(e.count * app.nav.height) {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "updir":
if app.ui.cmdPrefix != "" && app.ui.cmdPrefix != ">" {
normal(app)
}
for i := 0; i < e.count; i++ {
if err := app.nav.updir(); err != nil {
app.ui.echoerrf("%s", err)
return
}
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
onChdir(app)
case "open":
if app.ui.cmdPrefix != "" && app.ui.cmdPrefix != ">" {
normal(app)
}
curr, err := app.nav.currFile()
if err != nil {
app.ui.echoerrf("opening: %s", err)
return
}
if curr.IsDir() {
err := app.nav.open()
if err != nil {
app.ui.echoerrf("opening directory: %s", err)
return
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
onChdir(app)
return
}
if gSelectionPath != "" {
out, err := os.Create(gSelectionPath)
if err != nil {
log.Printf("opening selection file: %s", err)
return
}
defer out.Close()
var path string
if list, err := app.nav.currFileOrSelections(); err == nil {
path = strings.Join(list, "\n")
} else {
return
}
_, err = out.WriteString(path)
if err != nil {
log.Printf("writing selection file: %s", err)
}
app.quitChan <- struct{}{}
return
}
if cmd, ok := gOpts.cmds["open"]; ok {
cmd.eval(app, e.args)
}
case "quit":
app.quitChan <- struct{}{}
case "top":
if app.ui.cmdPrefix != "" && app.ui.cmdPrefix != ">" {
normal(app)
}
if app.nav.top() {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "bottom":
if app.ui.cmdPrefix != "" && app.ui.cmdPrefix != ">" {
normal(app)
}
if app.nav.bottom() {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "toggle":
if len(e.args) == 0 {
app.nav.toggle()
} else {
dir := app.nav.currDir()
for _, path := range e.args {
if !filepath.IsAbs(path) {
path = filepath.Join(dir.path, path)
}
if _, err := os.Lstat(path); !os.IsNotExist(err) {
app.nav.toggleSelection(path)
} else {
app.ui.echoerrf("toggle: %s", err)
}
}
}
case "invert":
app.nav.invert()
case "unselect":
app.nav.unselect()
case "copy":
if err := app.nav.save(true); err != nil {
app.ui.echoerrf("copy: %s", err)
return
}
app.nav.unselect()
if gSingleMode {
if err := app.nav.sync(); err != nil {
app.ui.echoerrf("copy: %s", err)
return
}
} else {
if err := remote("send sync"); err != nil {
app.ui.echoerrf("copy: %s", err)
return
}
}
app.ui.loadFileInfo(app.nav)
case "cut":
if err := app.nav.save(false); err != nil {
app.ui.echoerrf("cut: %s", err)
return
}
app.nav.unselect()
if gSingleMode {
if err := app.nav.sync(); err != nil {
app.ui.echoerrf("cut: %s", err)
return
}
} else {
if err := remote("send sync"); err != nil {
app.ui.echoerrf("cut: %s", err)
return
}
}
app.ui.loadFileInfo(app.nav)
case "paste":
if cmd, ok := gOpts.cmds["paste"]; ok {
cmd.eval(app, e.args)
} else if err := app.nav.paste(app.ui); err != nil {
app.ui.echoerrf("paste: %s", err)
return
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
case "delete":
if cmd, ok := gOpts.cmds["delete"]; ok {
cmd.eval(app, e.args)
app.nav.unselect()
if gSingleMode {
app.nav.renew()
app.ui.loadFile(app.nav, true)
} else {
if err := remote("send load"); err != nil {
app.ui.echoerrf("delete: %s", err)
return
}
}
} else {
list, err := app.nav.currFileOrSelections()
if err != nil {
app.ui.echoerrf("delete: %s", err)
return
}
if len(list) == 1 {
app.ui.cmdPrefix = "delete '" + list[0] + "' ? [y/N] "
} else {
app.ui.cmdPrefix = "delete " + strconv.Itoa(len(list)) + " items? [y/N] "
}
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
case "clear":
if err := saveFiles(nil, false); err != nil {
app.ui.echoerrf("clear: %s", err)
return
}
if gSingleMode {
if err := app.nav.sync(); err != nil {
app.ui.echoerrf("clear: %s", err)
return
}
} else {
if err := remote("send sync"); err != nil {
app.ui.echoerrf("clear: %s", err)
return
}
}
app.ui.loadFileInfo(app.nav)
case "draw":
case "redraw":
app.ui.renew()
app.ui.screen.Sync()
if app.nav.height != app.ui.wins[0].h {
app.nav.height = app.ui.wins[0].h
app.nav.regCache = make(map[string]*reg)
}
app.ui.loadFile(app.nav, true)
case "load":
app.nav.renew()
app.ui.loadFile(app.nav, true)
case "reload":
if err := app.nav.reload(); err != nil {
app.ui.echoerrf("reload: %s", err)
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
case "read":
app.ui.cmdPrefix = ":"
app.ui.loadFileInfo(app.nav)
case "shell":
app.ui.cmdPrefix = "$"
app.ui.loadFileInfo(app.nav)
case "shell-pipe":
app.ui.cmdPrefix = "%"
app.ui.loadFileInfo(app.nav)
case "shell-wait":
app.ui.cmdPrefix = "!"
app.ui.loadFileInfo(app.nav)
case "shell-async":
app.ui.cmdPrefix = "&"
app.ui.loadFileInfo(app.nav)
case "find":
app.ui.cmdPrefix = "find: "
app.nav.findBack = false
app.ui.loadFileInfo(app.nav)
case "find-back":
app.ui.cmdPrefix = "find-back: "
app.nav.findBack = true
app.ui.loadFileInfo(app.nav)
case "find-next":
dir := app.nav.currDir()
old := dir.ind
for i := 0; i < e.count; i++ {
if app.nav.findBack {
app.nav.findPrev()
} else {
app.nav.findNext()
}
}
if old != dir.ind {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "find-prev":
dir := app.nav.currDir()
old := dir.ind
for i := 0; i < e.count; i++ {
if app.nav.findBack {
app.nav.findNext()
} else {
app.nav.findPrev()
}
}
if old != dir.ind {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "search":
app.ui.cmdPrefix = "/"
dir := app.nav.currDir()
app.nav.searchInd = dir.ind
app.nav.searchPos = dir.pos
app.nav.searchBack = false
app.ui.loadFileInfo(app.nav)
case "search-back":
app.ui.cmdPrefix = "?"
dir := app.nav.currDir()
app.nav.searchInd = dir.ind
app.nav.searchPos = dir.pos
app.nav.searchBack = true
app.ui.loadFileInfo(app.nav)
case "search-next":
for i := 0; i < e.count; i++ {
if app.nav.searchBack {
if moved, err := app.nav.searchPrev(); err != nil {
app.ui.echoerrf("search-back: %s: %s", err, app.nav.search)
} else if moved {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
} else {
if moved, err := app.nav.searchNext(); err != nil {
app.ui.echoerrf("search: %s: %s", err, app.nav.search)
} else if moved {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
}
}
case "search-prev":
for i := 0; i < e.count; i++ {
if app.nav.searchBack {
if moved, err := app.nav.searchNext(); err != nil {
app.ui.echoerrf("search-back: %s: %s", err, app.nav.search)
} else if moved {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
} else {
if moved, err := app.nav.searchPrev(); err != nil {
app.ui.echoerrf("search: %s: %s", err, app.nav.search)
} else if moved {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
}
}
case "mark-save":
app.ui.cmdPrefix = "mark-save: "
case "mark-load":
app.ui.menuBuf = listMarks(app.nav.marks)
app.ui.cmdPrefix = "mark-load: "
case "mark-remove":
app.ui.menuBuf = listMarks(app.nav.marks)
app.ui.cmdPrefix = "mark-remove: "
case "rename":
if cmd, ok := gOpts.cmds["rename"]; ok {
cmd.eval(app, e.args)
if gSingleMode {
app.nav.renew()
app.ui.loadFile(app.nav, true)
} else {
if err := remote("send load"); err != nil {
app.ui.echoerrf("rename: %s", err)
return
}
}
} else {
curr, err := app.nav.currFile()
if err != nil {
app.ui.echoerrf("rename: %s:", err)
return
}
app.ui.cmdPrefix = "rename: "
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(curr.Name())...)
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
case "sync":
if err := app.nav.sync(); err != nil {
app.ui.echoerrf("sync: %s", err)
}
case "echo":
app.ui.echo(strings.Join(e.args, " "))
case "echomsg":
app.ui.echomsg(strings.Join(e.args, " "))
case "echoerr":
app.ui.echoerr(strings.Join(e.args, " "))
case "cd":
path := "~"
if len(e.args) > 0 {
path = e.args[0]
}
wd, err := os.Getwd()
if err != nil {
log.Printf("getting current directory: %s", err)
}
if err := app.nav.cd(path); err != nil {
app.ui.echoerrf("%s", err)
return
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
if !filepath.IsAbs(path) {
path = filepath.Join(wd, path)
} else {
path = filepath.Clean(path)
}
if wd != path {
app.nav.marks["'"] = wd
onChdir(app)
}
case "select":
if len(e.args) != 1 {
app.ui.echoerr("select: requires an argument")
return
}
wd, err := os.Getwd()
if err != nil {
log.Printf("getting current directory: %s", err)
}
if err := app.nav.sel(e.args[0]); err != nil {
app.ui.echoerrf("%s", err)
return
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
path := filepath.Dir(e.args[0])
if !filepath.IsAbs(path) {
path = filepath.Join(wd, path)
} else {
path = filepath.Clean(path)
}
if wd != path {
app.nav.marks["'"] = wd
onChdir(app)
}
case "glob-select":
if len(e.args) != 1 {
app.ui.echoerr("glob-select: requires a pattern to match")
return
}
if err := app.nav.globSel(e.args[0], false); err != nil {
app.ui.echoerrf("%s", err)
return
}
case "glob-unselect":
if len(e.args) != 1 {
app.ui.echoerr("glob-unselect: requires a pattern to match")
return
}
if err := app.nav.globSel(e.args[0], true); err != nil {
app.ui.echoerrf("%s", err)
return
}
case "source":
if len(e.args) != 1 {
app.ui.echoerr("source: requires an argument")
return
}
app.readFile(replaceTilde(e.args[0]))
app.ui.loadFileInfo(app.nav)
case "push":
if len(e.args) != 1 {
app.ui.echoerr("push: requires an argument")
return
}
log.Println("pushing keys", e.args[0])
for _, val := range splitKeys(e.args[0]) {
app.ui.keyChan <- val
}
case "cmd-insert":
if len(e.args) == 0 {
return
}
// Reset the completition menu as in bash/vim
// and update the pertinent variables
if app.ui.menuBuf != nil {
app.ui.menuBuf = nil
app.ui.menuSelected = -2
}
insert(app, e.args[0])
case "cmd-escape":
if app.ui.cmdPrefix == ">" {
return
}
if gOpts.incsearch && (app.ui.cmdPrefix == "/" || app.ui.cmdPrefix == "?") {
dir := app.nav.currDir()
dir.pos = app.nav.searchPos
if dir.ind != app.nav.searchInd {
dir.ind = app.nav.searchInd
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
}
normal(app)
case "cmd-complete":
var matches []string
switch app.ui.cmdPrefix {
case ":":
matches, app.ui.cmdAccLeft = completeCmd(app.ui.cmdAccLeft)
case "/", "?":
matches, app.ui.cmdAccLeft = completeFile(app.ui.cmdAccLeft)
case "$", "%", "!", "&":
matches, app.ui.cmdAccLeft = completeShell(app.ui.cmdAccLeft)
default:
return
}
app.ui.draw(app.nav)
// If there are no multiple matches
// we do not need to show the list of matches
if len(matches) <= 1 {
app.ui.menuBuf = nil
return
}
if b, err := listMatches(app.ui.screen, matches); err != nil {
app.ui.echoerrf("cmd-complete: %s", err)
} else {
app.ui.menuBuf = b
}
case "cmd-menu-complete":
var target []rune
// target will store the current menu query
// note that, as we will store the current selected value
// in cmdAccLeft, we can not call the complete function with its
// value, as it is already a final completition result
if app.ui.menuBuf == nil {
target = app.ui.cmdAccLeft
app.ui.cmdTmp = app.ui.cmdAccLeft
} else {
target = app.ui.cmdTmp
}
var matches []string
switch app.ui.cmdPrefix {
case ":":
matches, app.ui.cmdAccLeft = completeCmd(target)
case "/", "?":
matches, app.ui.cmdAccLeft = completeFile(target)
case "$", "%", "!", "&":
matches, app.ui.cmdAccLeft = completeShell(target)
default:
return
}
app.ui.draw(app.nav)
if len(matches) > 1 {
step := 1
// Check if the tab-selecttion was inited
if app.ui.menuSelected == -2 {
app.ui.menuSelected = -1
} else {
app.ui.menuSelected = mod(app.ui.menuSelected+step, len(matches))
}
if err := listMatchesMenu(app.ui, matches); err != nil {
app.ui.echoerrf("cmd-menu-complete: %s", err)
}
} else {
app.ui.menuBuf = nil
app.ui.menuSelected = -2
}
case "cmd-menu-complete-back":
var target []rune
if app.ui.menuBuf == nil {
target = app.ui.cmdAccLeft
app.ui.cmdTmp = app.ui.cmdAccLeft
} else {
target = app.ui.cmdTmp
}
var matches []string
switch app.ui.cmdPrefix {
case ":":
matches, app.ui.cmdAccLeft = completeCmd(target)
case "/", "?":
matches, app.ui.cmdAccLeft = completeFile(target)
case "$", "%", "!", "&":
matches, app.ui.cmdAccLeft = completeShell(target)
default:
return
}
app.ui.draw(app.nav)
if len(matches) > 1 {
step := -1
// Check if the tab-selecttion was inited
if app.ui.menuSelected == -2 {
app.ui.menuSelected = -1
} else {
app.ui.menuSelected = mod(app.ui.menuSelected+step, len(matches))
}
if err := listMatchesMenu(app.ui, matches); err != nil {
app.ui.echoerrf("cmd-menu-complete-back: %s", err)
}
} else {
app.ui.menuBuf = nil
app.ui.menuSelected = -2
}
case "cmd-enter":
s := string(append(app.ui.cmdAccLeft, app.ui.cmdAccRight...))
if len(s) == 0 {
return
}
app.ui.menuBuf = nil
app.ui.menuSelected = -2
app.ui.cmdAccLeft = nil
app.ui.cmdAccRight = nil
app.ui.cmdTmp = nil
switch app.ui.cmdPrefix {
case ":":
log.Printf("command: %s", s)
app.ui.cmdPrefix = ""
app.cmdHistory = append(app.cmdHistory, cmdItem{":", s})
p := newParser(strings.NewReader(s))
for p.parse() {
p.expr.eval(app, nil)
}
if p.err != nil {
app.ui.echoerrf("%s", p.err)
}
case "$":
log.Printf("shell: %s", s)
app.ui.cmdPrefix = ""
app.cmdHistory = append(app.cmdHistory, cmdItem{"$", s})
app.runShell(s, nil, "$")
case "%":
log.Printf("shell-pipe: %s", s)
app.cmdHistory = append(app.cmdHistory, cmdItem{"%", s})
app.runShell(s, nil, "%")
case ">":
io.WriteString(app.cmdIn, s+"\n")
app.cmdOutBuf = nil
case "!":
log.Printf("shell-wait: %s", s)
app.ui.cmdPrefix = ""
app.cmdHistory = append(app.cmdHistory, cmdItem{"!", s})
app.runShell(s, nil, "!")
case "&":
log.Printf("shell-async: %s", s)
app.ui.cmdPrefix = ""
app.cmdHistory = append(app.cmdHistory, cmdItem{"&", s})
app.runShell(s, nil, "&")
case "/":
dir := app.nav.currDir()
old := dir.ind
if gOpts.incsearch {
dir.ind = app.nav.searchInd
dir.pos = app.nav.searchPos
}
log.Printf("search: %s", s)
app.ui.cmdPrefix = ""
app.nav.search = s
if _, err := app.nav.searchNext(); err != nil {
app.ui.echoerrf("search: %s: %s", err, app.nav.search)
} else if old != dir.ind {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "?":
dir := app.nav.currDir()
old := dir.ind
if gOpts.incsearch {
dir.ind = app.nav.searchInd
dir.pos = app.nav.searchPos
}
log.Printf("search-back: %s", s)
app.ui.cmdPrefix = ""
app.nav.search = s
if _, err := app.nav.searchPrev(); err != nil {
app.ui.echoerrf("search-back: %s: %s", err, app.nav.search)
} else if old != dir.ind {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "find: ":
app.ui.cmdPrefix = ""
if moved, found := app.nav.findNext(); !found {
app.ui.echoerrf("find: pattern not found: %s", app.nav.find)
} else if moved {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "find-back: ":
app.ui.cmdPrefix = ""
if moved, found := app.nav.findPrev(); !found {
app.ui.echoerrf("find-back: pattern not found: %s", app.nav.find)
} else if moved {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
case "rename: ":
app.ui.cmdPrefix = ""
if curr, err := app.nav.currFile(); err != nil {
app.ui.echoerrf("rename: %s", err)
} else {
wd, err := os.Getwd()
if err != nil {
log.Printf("getting current directory: %s", err)
return
}
oldPath := filepath.Join(wd, curr.Name())
newPath := filepath.Clean(s)
if !filepath.IsAbs(newPath) {
newPath = filepath.Join(wd, newPath)
}
if oldPath == newPath {
return
}
app.nav.renameOldPath = oldPath
app.nav.renameNewPath = newPath
newDir := filepath.Dir(newPath)
if _, err := os.Stat(newDir); os.IsNotExist(err) {
app.ui.cmdPrefix = "create '" + newDir + "' ? [y/N] "
return
}
oldStat, err := os.Lstat(oldPath)
if err != nil {
app.ui.echoerrf("rename: %s", err)
return
}
if newStat, err := os.Lstat(newPath); !os.IsNotExist(err) && !os.SameFile(oldStat, newStat) {
app.ui.cmdPrefix = "replace '" + newPath + "' ? [y/N] "
return
}
if err := app.nav.rename(); err != nil {
app.ui.echoerrf("rename: %s", err)
return
}
if gSingleMode {
app.nav.renew()
app.ui.loadFile(app.nav, true)
} else {
if err := remote("send load"); err != nil {
app.ui.echoerrf("rename: %s", err)
return
}
}
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
}
default:
log.Printf("entering unknown execution prefix: %q", app.ui.cmdPrefix)
}
case "cmd-history-next":
if app.ui.cmdPrefix == "" || app.ui.cmdPrefix == ">" {
return
}
if app.cmdHistoryInd > 0 {
app.cmdHistoryInd--
}
if app.cmdHistoryInd == 0 {
app.ui.menuBuf = nil
app.ui.menuSelected = -2
app.ui.cmdAccLeft = nil
app.ui.cmdAccRight = nil
app.ui.cmdTmp = nil
app.ui.cmdPrefix = ":"
return
}
cmd := app.cmdHistory[len(app.cmdHistory)-app.cmdHistoryInd]
app.ui.cmdPrefix = cmd.prefix
app.ui.cmdAccLeft = []rune(cmd.value)
app.ui.cmdAccRight = nil
app.ui.cmdTmp = nil
app.ui.menuBuf = nil
app.ui.menuSelected = -2
case "cmd-history-prev":
if app.ui.cmdPrefix == ">" {
return
}
if app.ui.cmdPrefix == "" {
app.cmdHistoryInd = 0
}
if app.cmdHistoryInd == len(app.cmdHistory) {
return
}
app.cmdHistoryInd++
cmd := app.cmdHistory[len(app.cmdHistory)-app.cmdHistoryInd]
app.ui.cmdPrefix = cmd.prefix
app.ui.cmdAccLeft = []rune(cmd.value)
app.ui.cmdAccRight = nil
app.ui.cmdTmp = nil
app.ui.menuBuf = nil
app.ui.menuSelected = -2
case "cmd-delete":
if len(app.ui.cmdAccRight) == 0 {
return
}
app.ui.cmdAccRight = app.ui.cmdAccRight[1:]
update(app)
case "cmd-delete-back":
if len(app.ui.cmdAccLeft) == 0 {
return
}
app.ui.cmdAccLeft = app.ui.cmdAccLeft[:len(app.ui.cmdAccLeft)-1]
update(app)
case "cmd-left":
if len(app.ui.cmdAccLeft) == 0 {
return
}
app.ui.cmdAccRight = append([]rune{app.ui.cmdAccLeft[len(app.ui.cmdAccLeft)-1]}, app.ui.cmdAccRight...)
app.ui.cmdAccLeft = app.ui.cmdAccLeft[:len(app.ui.cmdAccLeft)-1]
case "cmd-right":
if len(app.ui.cmdAccRight) == 0 {
return
}
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, app.ui.cmdAccRight[0])
app.ui.cmdAccRight = app.ui.cmdAccRight[1:]
case "cmd-home":
app.ui.cmdAccRight = append(app.ui.cmdAccLeft, app.ui.cmdAccRight...)
app.ui.cmdAccLeft = nil
case "cmd-end":
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, app.ui.cmdAccRight...)
app.ui.cmdAccRight = nil
case "cmd-delete-home":
if len(app.ui.cmdAccLeft) == 0 {
return
}
app.ui.cmdYankBuf = app.ui.cmdAccLeft
app.ui.cmdAccLeft = nil
update(app)
case "cmd-delete-end":
if len(app.ui.cmdAccRight) == 0 {
return
}
app.ui.cmdYankBuf = app.ui.cmdAccRight
app.ui.cmdAccRight = nil
update(app)
case "cmd-delete-unix-word":
ind := strings.LastIndex(strings.TrimRight(string(app.ui.cmdAccLeft), " "), " ") + 1
app.ui.cmdYankBuf = []rune(string(app.ui.cmdAccLeft)[ind:])
app.ui.cmdAccLeft = []rune(string(app.ui.cmdAccLeft)[:ind])
update(app)
case "cmd-yank":
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, app.ui.cmdYankBuf...)
update(app)
case "cmd-transpose":
if len(app.ui.cmdAccLeft) < 2 {
return
}
app.ui.cmdAccLeft[len(app.ui.cmdAccLeft)-1], app.ui.cmdAccLeft[len(app.ui.cmdAccLeft)-2] = app.ui.cmdAccLeft[len(app.ui.cmdAccLeft)-2], app.ui.cmdAccLeft[len(app.ui.cmdAccLeft)-1]
update(app)
case "cmd-interrupt":
if app.cmd != nil {
app.cmd.Process.Kill()
}
normal(app)
case "cmd-word":
if len(app.ui.cmdAccRight) == 0 {
return
}
loc := reWordEnd.FindStringSubmatchIndex(string(app.ui.cmdAccRight))
if loc == nil {
return
}
ind := loc[3]
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(string(app.ui.cmdAccRight)[:ind])...)
app.ui.cmdAccRight = []rune(string(app.ui.cmdAccRight)[ind:])
case "cmd-word-back":
if len(app.ui.cmdAccLeft) == 0 {
return
}
locs := reWordBeg.FindAllStringSubmatchIndex(string(app.ui.cmdAccLeft), -1)
if locs == nil {
return
}
ind := locs[len(locs)-1][3]
old := app.ui.cmdAccRight
app.ui.cmdAccRight = append([]rune{}, []rune(string(app.ui.cmdAccLeft)[ind:])...)
app.ui.cmdAccRight = append(app.ui.cmdAccRight, old...)
app.ui.cmdAccLeft = []rune(string(app.ui.cmdAccLeft)[:ind])
case "cmd-capitalize-word":
if len(app.ui.cmdAccRight) == 0 {
return
}
ind := 0
for ; ind < len(app.ui.cmdAccRight) && unicode.IsSpace(app.ui.cmdAccRight[ind]); ind++ {
}
if ind >= len(app.ui.cmdAccRight) {
return
}
app.ui.cmdAccRight[ind] = unicode.ToUpper(app.ui.cmdAccRight[ind])
loc := reWordEnd.FindStringSubmatchIndex(string(app.ui.cmdAccRight))
if loc == nil {
return
}
ind = loc[3]
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(string(app.ui.cmdAccRight)[:ind])...)
app.ui.cmdAccRight = []rune(string(app.ui.cmdAccRight)[ind:])
update(app)
case "cmd-delete-word":
if len(app.ui.cmdAccRight) == 0 {
return
}
loc := reWordEnd.FindStringSubmatchIndex(string(app.ui.cmdAccRight))
if loc == nil {
return
}
ind := loc[3]
app.ui.cmdAccRight = []rune(string(app.ui.cmdAccRight)[ind:])
update(app)
case "cmd-uppercase-word":
if len(app.ui.cmdAccRight) == 0 {
return
}
loc := reWordEnd.FindStringSubmatchIndex(string(app.ui.cmdAccRight))
if loc == nil {
return
}
ind := loc[3]
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(strings.ToUpper(string(app.ui.cmdAccRight)[:ind]))...)
app.ui.cmdAccRight = []rune(string(app.ui.cmdAccRight)[ind:])
update(app)
case "cmd-lowercase-word":
if len(app.ui.cmdAccRight) == 0 {
return
}
loc := reWordEnd.FindStringSubmatchIndex(string(app.ui.cmdAccRight))
if loc == nil {
return
}
ind := loc[3]
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(strings.ToLower(string(app.ui.cmdAccRight)[:ind]))...)
app.ui.cmdAccRight = []rune(string(app.ui.cmdAccRight)[ind:])
update(app)
case "cmd-transpose-word":
if len(app.ui.cmdAccLeft) == 0 {
return
}
locs := reWord.FindAllStringIndex(string(app.ui.cmdAccLeft), -1)
if len(locs) < 2 {
return
}
if len(app.ui.cmdAccRight) > 0 {
loc := reWordEnd.FindStringSubmatchIndex(string(app.ui.cmdAccRight))
if loc != nil {
ind := loc[3]
app.ui.cmdAccLeft = append(app.ui.cmdAccLeft, []rune(string(app.ui.cmdAccRight)[:ind])...)
app.ui.cmdAccRight = []rune(string(app.ui.cmdAccRight)[ind:])
}
}
locs = reWord.FindAllStringIndex(string(app.ui.cmdAccLeft), -1)
beg1, end1 := locs[len(locs)-2][0], locs[len(locs)-2][1]
beg2, end2 := locs[len(locs)-1][0], locs[len(locs)-1][1]
var acc []rune
acc = append(acc, []rune(string(app.ui.cmdAccLeft)[:beg1])...)
acc = append(acc, []rune(string(app.ui.cmdAccLeft)[beg2:end2])...)
acc = append(acc, []rune(string(app.ui.cmdAccLeft)[end1:beg2])...)
acc = append(acc, []rune(string(app.ui.cmdAccLeft)[beg1:end1])...)
acc = append(acc, []rune(string(app.ui.cmdAccLeft)[end2:])...)
app.ui.cmdAccLeft = acc
update(app)
default:
cmd, ok := gOpts.cmds[e.name]
if !ok {
app.ui.echoerrf("command not found: %s", e.name)
return
}
cmd.eval(app, e.args)
}
}
func (e *execExpr) eval(app *app, args []string) {
switch e.prefix {
case "$":
log.Printf("shell: %s -- %s", e, args)
app.runShell(e.value, args, e.prefix)
case "%":
log.Printf("shell-pipe: %s -- %s", e, args)
app.runShell(e.value, args, e.prefix)
case "!":
log.Printf("shell-wait: %s -- %s", e, args)
app.runShell(e.value, args, e.prefix)
case "&":
log.Printf("shell-async: %s -- %s", e, args)
app.runShell(e.value, args, e.prefix)
default:
log.Printf("evaluating unknown execution prefix: %q", e.prefix)
}
}
func (e *listExpr) eval(app *app, args []string) {
for i := 0; i < e.count; i++ {
for _, expr := range e.exprs {
expr.eval(app, nil)
}
}
}