2016-08-13 12:49:04 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2018-02-10 16:37:37 +00:00
|
|
|
"io"
|
2016-08-13 12:49:04 +00:00
|
|
|
"log"
|
|
|
|
"os"
|
2016-09-06 20:05:18 +00:00
|
|
|
"path/filepath"
|
2016-08-24 10:08:49 +00:00
|
|
|
"sort"
|
2016-08-28 00:45:05 +00:00
|
|
|
"strconv"
|
2016-08-13 12:49:04 +00:00
|
|
|
"strings"
|
|
|
|
"text/tabwriter"
|
2018-05-05 13:26:16 +00:00
|
|
|
"time"
|
2016-12-15 09:26:06 +00:00
|
|
|
"unicode"
|
2016-08-28 00:45:05 +00:00
|
|
|
"unicode/utf8"
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2020-03-20 18:59:30 +00:00
|
|
|
"github.com/doronbehar/termbox-go"
|
2020-04-13 15:24:15 +00:00
|
|
|
"github.com/mattn/go-runewidth"
|
2016-08-13 12:49:04 +00:00
|
|
|
)
|
|
|
|
|
2018-04-15 15:18:39 +00:00
|
|
|
const gEscapeCode = 27
|
2016-08-28 00:45:05 +00:00
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
var gKeyVal = map[termbox.Key]string{
|
|
|
|
termbox.KeyF1: "<f-1>",
|
|
|
|
termbox.KeyF2: "<f-2>",
|
|
|
|
termbox.KeyF3: "<f-3>",
|
|
|
|
termbox.KeyF4: "<f-4>",
|
|
|
|
termbox.KeyF5: "<f-5>",
|
|
|
|
termbox.KeyF6: "<f-6>",
|
|
|
|
termbox.KeyF7: "<f-7>",
|
|
|
|
termbox.KeyF8: "<f-8>",
|
|
|
|
termbox.KeyF9: "<f-9>",
|
|
|
|
termbox.KeyF10: "<f-10>",
|
|
|
|
termbox.KeyF11: "<f-11>",
|
|
|
|
termbox.KeyF12: "<f-12>",
|
|
|
|
termbox.KeyInsert: "<insert>",
|
|
|
|
termbox.KeyDelete: "<delete>",
|
|
|
|
termbox.KeyHome: "<home>",
|
|
|
|
termbox.KeyEnd: "<end>",
|
|
|
|
termbox.KeyPgup: "<pgup>",
|
|
|
|
termbox.KeyPgdn: "<pgdn>",
|
|
|
|
termbox.KeyArrowUp: "<up>",
|
|
|
|
termbox.KeyArrowDown: "<down>",
|
|
|
|
termbox.KeyArrowLeft: "<left>",
|
|
|
|
termbox.KeyArrowRight: "<right>",
|
|
|
|
termbox.KeyCtrlSpace: "<c-space>",
|
|
|
|
termbox.KeyCtrlA: "<c-a>",
|
|
|
|
termbox.KeyCtrlB: "<c-b>",
|
|
|
|
termbox.KeyCtrlC: "<c-c>",
|
|
|
|
termbox.KeyCtrlD: "<c-d>",
|
|
|
|
termbox.KeyCtrlE: "<c-e>",
|
|
|
|
termbox.KeyCtrlF: "<c-f>",
|
|
|
|
termbox.KeyCtrlG: "<c-g>",
|
|
|
|
termbox.KeyBackspace: "<bs>",
|
|
|
|
termbox.KeyTab: "<tab>",
|
|
|
|
termbox.KeyCtrlJ: "<c-j>",
|
|
|
|
termbox.KeyCtrlK: "<c-k>",
|
|
|
|
termbox.KeyCtrlL: "<c-l>",
|
|
|
|
termbox.KeyEnter: "<enter>",
|
|
|
|
termbox.KeyCtrlN: "<c-n>",
|
|
|
|
termbox.KeyCtrlO: "<c-o>",
|
|
|
|
termbox.KeyCtrlP: "<c-p>",
|
|
|
|
termbox.KeyCtrlQ: "<c-q>",
|
|
|
|
termbox.KeyCtrlR: "<c-r>",
|
|
|
|
termbox.KeyCtrlS: "<c-s>",
|
|
|
|
termbox.KeyCtrlT: "<c-t>",
|
|
|
|
termbox.KeyCtrlU: "<c-u>",
|
|
|
|
termbox.KeyCtrlV: "<c-v>",
|
|
|
|
termbox.KeyCtrlW: "<c-w>",
|
|
|
|
termbox.KeyCtrlX: "<c-x>",
|
|
|
|
termbox.KeyCtrlY: "<c-y>",
|
|
|
|
termbox.KeyCtrlZ: "<c-z>",
|
|
|
|
termbox.KeyEsc: "<esc>",
|
|
|
|
termbox.KeyCtrlBackslash: "<c-\\>",
|
|
|
|
termbox.KeyCtrlRsqBracket: "<c-]>",
|
|
|
|
termbox.KeyCtrl6: "<c-6>",
|
|
|
|
termbox.KeyCtrlSlash: "<c-/>",
|
|
|
|
termbox.KeySpace: "<space>",
|
|
|
|
termbox.KeyBackspace2: "<bs2>",
|
2016-09-18 16:21:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var gValKey map[string]termbox.Key
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
gValKey = make(map[string]termbox.Key)
|
|
|
|
for k, v := range gKeyVal {
|
2018-05-20 17:30:41 +00:00
|
|
|
gValKey[v] = k
|
2016-09-18 16:21:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type win struct {
|
2017-11-19 18:55:13 +00:00
|
|
|
w, h, x, y int
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func newWin(w, h, x, y int) *win {
|
|
|
|
return &win{w, h, x, y}
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (win *win) renew(w, h, x, y int) {
|
2017-11-19 18:55:13 +00:00
|
|
|
win.w, win.h, win.x, win.y = w, h, x, y
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2018-02-22 15:18:30 +00:00
|
|
|
func printLength(s string) int {
|
|
|
|
ind := 0
|
|
|
|
off := 0
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
|
|
r, w := utf8.DecodeRuneInString(s[i:])
|
|
|
|
|
|
|
|
if r == gEscapeCode && i+1 < len(s) && s[i+1] == '[' {
|
|
|
|
j := strings.IndexByte(s[i:min(len(s), i+32)], 'm')
|
|
|
|
if j == -1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
i += j
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
i += w - 1
|
|
|
|
|
|
|
|
if r == '\t' {
|
|
|
|
ind += gOpts.tabstop - (ind-off)%gOpts.tabstop
|
|
|
|
} else {
|
|
|
|
ind += runewidth.RuneWidth(r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ind
|
|
|
|
}
|
|
|
|
|
2017-11-22 14:17:23 +00:00
|
|
|
func (win *win) print(x, y int, fg, bg termbox.Attribute, s string) (termbox.Attribute, termbox.Attribute) {
|
2016-08-13 12:49:04 +00:00
|
|
|
off := x
|
2016-08-28 00:45:05 +00:00
|
|
|
for i := 0; i < len(s); i++ {
|
|
|
|
r, w := utf8.DecodeRuneInString(s[i:])
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
if r == gEscapeCode && i+1 < len(s) && s[i+1] == '[' {
|
2016-12-17 12:27:27 +00:00
|
|
|
j := strings.IndexByte(s[i:min(len(s), i+32)], 'm')
|
2016-11-29 14:00:40 +00:00
|
|
|
if j == -1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-11-22 14:17:23 +00:00
|
|
|
fg, bg = applyAnsiCodes(s[i+2:i+j], fg, bg)
|
2016-11-29 14:00:40 +00:00
|
|
|
|
|
|
|
i += j
|
|
|
|
continue
|
2016-08-28 00:45:05 +00:00
|
|
|
}
|
|
|
|
|
2018-03-22 15:07:17 +00:00
|
|
|
if x < win.w {
|
|
|
|
termbox.SetCell(win.x+x, win.y+y, r, fg, bg)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2016-08-28 00:45:05 +00:00
|
|
|
i += w - 1
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-08-28 00:45:05 +00:00
|
|
|
if r == '\t' {
|
2020-07-03 22:08:35 +00:00
|
|
|
s := gOpts.tabstop - (x-off)%gOpts.tabstop
|
|
|
|
for i := 0; i < s && x+i < win.w; i++ {
|
|
|
|
termbox.SetCell(win.x+x+i, win.y+y, ' ', fg, bg)
|
|
|
|
}
|
|
|
|
x += s
|
2016-08-13 12:49:04 +00:00
|
|
|
} else {
|
2016-12-22 19:58:55 +00:00
|
|
|
x += runewidth.RuneWidth(r)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
}
|
2017-11-22 14:17:23 +00:00
|
|
|
|
|
|
|
return fg, bg
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (win *win) printf(x, y int, fg, bg termbox.Attribute, format string, a ...interface{}) {
|
2016-08-13 12:49:04 +00:00
|
|
|
win.print(x, y, fg, bg, fmt.Sprintf(format, a...))
|
|
|
|
}
|
|
|
|
|
2018-01-11 17:46:07 +00:00
|
|
|
func (win *win) printLine(x, y int, fg, bg termbox.Attribute, s string) {
|
2016-08-13 12:49:04 +00:00
|
|
|
win.printf(x, y, fg, bg, "%s%*s", s, win.w-len(s), "")
|
|
|
|
}
|
|
|
|
|
2018-01-11 17:46:07 +00:00
|
|
|
func (win *win) printRight(y int, fg, bg termbox.Attribute, s string) {
|
|
|
|
win.print(win.w-len(s), y, fg, bg, s)
|
|
|
|
}
|
|
|
|
|
2018-02-10 15:59:19 +00:00
|
|
|
func (win *win) printReg(reg *reg) {
|
|
|
|
if reg == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-11-19 18:55:13 +00:00
|
|
|
fg, bg := termbox.ColorDefault, termbox.ColorDefault
|
|
|
|
|
2018-06-07 19:49:53 +00:00
|
|
|
if reg.loading {
|
2019-02-28 19:10:57 +00:00
|
|
|
fg = termbox.AttrReverse
|
2018-06-07 19:49:53 +00:00
|
|
|
win.print(2, 0, fg, bg, "loading...")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-02-10 15:59:19 +00:00
|
|
|
for i, l := range reg.lines {
|
2017-11-22 14:17:23 +00:00
|
|
|
fg, bg = win.print(2, i, fg, bg, l)
|
2017-11-19 18:55:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-14 20:16:31 +00:00
|
|
|
func fileInfo(f *file, d *dir) string {
|
|
|
|
var info string
|
|
|
|
|
|
|
|
path := filepath.Join(d.path, f.Name())
|
|
|
|
|
|
|
|
for _, s := range gOpts.info {
|
|
|
|
switch s {
|
|
|
|
case "size":
|
|
|
|
if !(gOpts.dircounts && f.IsDir()) {
|
|
|
|
info = fmt.Sprintf("%s %4s", info, humanize(f.Size()))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
if f.dirCount == -1 {
|
2018-04-14 20:16:31 +00:00
|
|
|
d, err := os.Open(path)
|
|
|
|
if err != nil {
|
2018-05-20 17:30:41 +00:00
|
|
|
f.dirCount = -2
|
2018-04-14 20:16:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
names, err := d.Readdirnames(1000)
|
|
|
|
d.Close()
|
|
|
|
|
|
|
|
if names == nil && err != io.EOF {
|
2018-05-20 17:30:41 +00:00
|
|
|
f.dirCount = -2
|
2018-04-14 20:16:31 +00:00
|
|
|
} else {
|
2018-05-20 17:30:41 +00:00
|
|
|
f.dirCount = len(names)
|
2018-04-14 20:16:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
2018-05-20 17:30:41 +00:00
|
|
|
case f.dirCount < 0:
|
2018-04-14 20:16:31 +00:00
|
|
|
info = fmt.Sprintf("%s ?", info)
|
2018-05-20 17:30:41 +00:00
|
|
|
case f.dirCount < 1000:
|
|
|
|
info = fmt.Sprintf("%s %4d", info, f.dirCount)
|
2018-04-14 20:16:31 +00:00
|
|
|
default:
|
|
|
|
info = fmt.Sprintf("%s 999+", info)
|
|
|
|
}
|
|
|
|
case "time":
|
|
|
|
info = fmt.Sprintf("%s %12s", info, f.ModTime().Format("Jan _2 15:04"))
|
2019-09-18 18:52:30 +00:00
|
|
|
case "atime":
|
|
|
|
info = fmt.Sprintf("%s %12s", info, f.accessTime.Format("Jan _2 15:04"))
|
|
|
|
case "ctime":
|
|
|
|
info = fmt.Sprintf("%s %12s", info, f.changeTime.Format("Jan _2 15:04"))
|
2018-04-14 20:16:31 +00:00
|
|
|
default:
|
|
|
|
log.Printf("unknown info type: %s", s)
|
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2018-04-14 20:16:31 +00:00
|
|
|
return info
|
|
|
|
}
|
|
|
|
|
2019-06-28 15:08:11 +00:00
|
|
|
func (win *win) printDir(dir *dir, selections map[string]int, saves map[string]bool, colors colorMap, icons iconMap) {
|
2018-04-14 20:16:31 +00:00
|
|
|
if win.w < 5 || dir == nil {
|
2017-11-19 18:55:13 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-01-11 16:25:48 +00:00
|
|
|
if dir.loading {
|
2019-03-03 19:29:34 +00:00
|
|
|
win.print(2, 0, termbox.AttrReverse, termbox.ColorDefault, "loading...")
|
2018-01-11 16:25:48 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-07-20 12:44:20 +00:00
|
|
|
if dir.noPerm {
|
|
|
|
win.print(2, 0, termbox.AttrReverse, termbox.ColorDefault, "permission denied")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
if len(dir.files) == 0 {
|
2019-03-03 19:29:34 +00:00
|
|
|
win.print(2, 0, termbox.AttrReverse, termbox.ColorDefault, "empty")
|
2016-08-13 12:49:04 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
beg := max(dir.ind-dir.pos, 0)
|
2018-05-20 17:30:41 +00:00
|
|
|
end := min(beg+win.h, len(dir.files))
|
2018-04-15 16:26:51 +00:00
|
|
|
|
|
|
|
if beg > end {
|
|
|
|
return
|
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2019-03-03 19:29:34 +00:00
|
|
|
var lnwidth int
|
|
|
|
var lnformat string
|
2019-01-27 14:31:29 +00:00
|
|
|
|
2019-01-31 18:51:17 +00:00
|
|
|
if gOpts.number || gOpts.relativenumber {
|
|
|
|
lnwidth = 1
|
2020-06-07 17:53:25 +00:00
|
|
|
if gOpts.number && gOpts.relativenumber {
|
|
|
|
lnwidth++
|
|
|
|
}
|
2019-01-27 14:31:29 +00:00
|
|
|
for j := 10; j < len(dir.files); j *= 10 {
|
|
|
|
lnwidth++
|
|
|
|
}
|
|
|
|
lnformat = fmt.Sprintf("%%%d.d ", lnwidth)
|
|
|
|
}
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
for i, f := range dir.files[beg:end] {
|
2019-03-03 19:29:34 +00:00
|
|
|
fg, bg := colors.get(f)
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2019-01-31 18:51:17 +00:00
|
|
|
if lnwidth > 0 {
|
2019-01-27 14:31:29 +00:00
|
|
|
var ln string
|
|
|
|
|
2020-06-07 17:53:25 +00:00
|
|
|
if gOpts.number && (!gOpts.relativenumber) {
|
2019-01-31 18:51:17 +00:00
|
|
|
ln = fmt.Sprintf(lnformat, i+1+beg)
|
|
|
|
} else if gOpts.relativenumber {
|
2020-06-07 17:53:25 +00:00
|
|
|
switch {
|
|
|
|
case i < dir.pos:
|
2019-01-31 18:51:17 +00:00
|
|
|
ln = fmt.Sprintf(lnformat, dir.pos-i)
|
2020-06-07 17:53:25 +00:00
|
|
|
case i > dir.pos:
|
2019-01-31 18:51:17 +00:00
|
|
|
ln = fmt.Sprintf(lnformat, i-dir.pos)
|
2020-06-07 17:53:25 +00:00
|
|
|
case gOpts.number:
|
|
|
|
ln = fmt.Sprintf(fmt.Sprintf("%%%d.d ", lnwidth-1), i+1+beg)
|
|
|
|
default:
|
|
|
|
ln = ""
|
2019-01-27 14:31:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
win.print(0, i, termbox.ColorYellow, bg, ln)
|
|
|
|
}
|
|
|
|
|
2016-09-06 20:05:18 +00:00
|
|
|
path := filepath.Join(dir.path, f.Name())
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2018-07-09 18:22:10 +00:00
|
|
|
if _, ok := selections[path]; ok {
|
2019-01-27 14:31:29 +00:00
|
|
|
win.print(lnwidth, i, fg, termbox.ColorMagenta, " ")
|
2018-06-27 18:15:34 +00:00
|
|
|
} else if cp, ok := saves[path]; ok {
|
|
|
|
if cp {
|
2019-01-27 14:31:29 +00:00
|
|
|
win.print(lnwidth, i, fg, termbox.ColorYellow, " ")
|
2016-11-07 20:32:19 +00:00
|
|
|
} else {
|
2019-01-27 14:31:29 +00:00
|
|
|
win.print(lnwidth, i, fg, termbox.ColorRed, " ")
|
2016-11-07 20:32:19 +00:00
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if i == dir.pos {
|
2016-12-18 18:51:27 +00:00
|
|
|
fg |= termbox.AttrReverse
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2016-10-20 20:45:06 +00:00
|
|
|
var s []rune
|
2016-08-13 12:49:04 +00:00
|
|
|
|
|
|
|
s = append(s, ' ')
|
|
|
|
|
2019-06-28 15:08:11 +00:00
|
|
|
if gOpts.icons {
|
|
|
|
s = append(s, []rune(icons.get(f))...)
|
|
|
|
s = append(s, ' ')
|
|
|
|
}
|
|
|
|
|
2016-10-20 20:45:06 +00:00
|
|
|
for _, r := range f.Name() {
|
|
|
|
s = append(s, r)
|
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-10-20 20:45:06 +00:00
|
|
|
w := runeSliceWidth(s)
|
2018-04-14 20:16:31 +00:00
|
|
|
|
|
|
|
if w > win.w-3 {
|
|
|
|
s = runeSliceWidthRange(s, 0, win.w-4)
|
2020-07-18 00:08:25 +00:00
|
|
|
s = append(s, []rune(gOpts.truncatechar)...)
|
2016-08-13 12:49:04 +00:00
|
|
|
} else {
|
2018-04-14 20:16:31 +00:00
|
|
|
for i := 0; i < win.w-3-w; i++ {
|
2016-12-22 19:58:55 +00:00
|
|
|
s = append(s, ' ')
|
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2018-04-14 20:16:31 +00:00
|
|
|
info := fileInfo(f, dir)
|
2017-06-03 11:12:43 +00:00
|
|
|
|
2018-04-14 20:16:31 +00:00
|
|
|
if len(info) > 0 && win.w-2 > 2*len(info) {
|
|
|
|
if win.w-2 > w+len(info) {
|
2020-06-07 17:53:25 +00:00
|
|
|
s = runeSliceWidthRange(s, 0, win.w-3-len(info)-lnwidth)
|
2018-04-14 20:16:31 +00:00
|
|
|
} else {
|
2020-06-07 17:53:25 +00:00
|
|
|
s = runeSliceWidthRange(s, 0, win.w-4-len(info)-lnwidth)
|
2020-07-18 00:08:25 +00:00
|
|
|
s = append(s, []rune(gOpts.truncatechar)...)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
2017-02-04 18:28:03 +00:00
|
|
|
for _, r := range info {
|
|
|
|
s = append(s, r)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-14 20:16:31 +00:00
|
|
|
s = append(s, ' ')
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2019-01-27 14:31:29 +00:00
|
|
|
win.print(lnwidth+1, i, fg, bg, string(s))
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type ui struct {
|
2017-11-19 18:55:13 +00:00
|
|
|
wins []*win
|
2018-02-10 17:56:59 +00:00
|
|
|
promptWin *win
|
2017-11-19 18:55:13 +00:00
|
|
|
msgWin *win
|
|
|
|
menuWin *win
|
|
|
|
msg string
|
2018-02-10 15:59:19 +00:00
|
|
|
regPrev *reg
|
2017-11-19 18:55:13 +00:00
|
|
|
dirPrev *dir
|
2018-04-12 15:04:37 +00:00
|
|
|
exprChan chan expr
|
2017-11-19 18:55:13 +00:00
|
|
|
keyChan chan string
|
|
|
|
evChan chan termbox.Event
|
|
|
|
menuBuf *bytes.Buffer
|
|
|
|
cmdPrefix string
|
|
|
|
cmdAccLeft []rune
|
|
|
|
cmdAccRight []rune
|
2018-05-20 17:30:41 +00:00
|
|
|
cmdYankBuf []rune
|
2017-11-19 18:55:13 +00:00
|
|
|
keyAcc []rune
|
|
|
|
keyCount []rune
|
2018-04-14 18:18:39 +00:00
|
|
|
colors colorMap
|
2019-06-28 15:08:11 +00:00
|
|
|
icons iconMap
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func getWidths(wtot int) []int {
|
|
|
|
rsum := 0
|
2018-05-20 17:30:41 +00:00
|
|
|
for _, r := range gOpts.ratios {
|
|
|
|
rsum += r
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wlen := len(gOpts.ratios)
|
|
|
|
widths := make([]int, wlen)
|
|
|
|
|
|
|
|
wsum := 0
|
|
|
|
for i := 0; i < wlen-1; i++ {
|
|
|
|
widths[i] = gOpts.ratios[i] * (wtot / rsum)
|
|
|
|
wsum += widths[i]
|
|
|
|
}
|
|
|
|
widths[wlen-1] = wtot - wsum
|
|
|
|
|
2018-04-15 16:26:51 +00:00
|
|
|
if gOpts.drawbox {
|
|
|
|
widths[wlen-1]--
|
|
|
|
}
|
|
|
|
|
2016-08-13 12:49:04 +00:00
|
|
|
return widths
|
|
|
|
}
|
|
|
|
|
2016-12-18 19:38:28 +00:00
|
|
|
func getWins() []*win {
|
2016-08-13 12:49:04 +00:00
|
|
|
wtot, htot := termbox.Size()
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
var wins []*win
|
2016-08-13 12:49:04 +00:00
|
|
|
|
|
|
|
widths := getWidths(wtot)
|
|
|
|
|
|
|
|
wacc := 0
|
|
|
|
wlen := len(widths)
|
|
|
|
for i := 0; i < wlen; i++ {
|
2018-04-15 16:26:51 +00:00
|
|
|
if gOpts.drawbox {
|
|
|
|
wins = append(wins, newWin(widths[i], htot-4, wacc+1, 2))
|
|
|
|
} else {
|
|
|
|
wins = append(wins, newWin(widths[i], htot-2, wacc, 1))
|
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
wacc += widths[i]
|
|
|
|
}
|
|
|
|
|
2016-12-18 19:38:28 +00:00
|
|
|
return wins
|
|
|
|
}
|
|
|
|
|
|
|
|
func newUI() *ui {
|
|
|
|
wtot, htot := termbox.Size()
|
|
|
|
|
2018-05-05 13:26:16 +00:00
|
|
|
evQueue := make(chan termbox.Event)
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
evQueue <- termbox.PollEvent()
|
|
|
|
}
|
|
|
|
}()
|
2016-12-15 09:26:06 +00:00
|
|
|
|
2018-05-05 13:26:16 +00:00
|
|
|
evChan := make(chan termbox.Event)
|
2016-12-15 09:26:06 +00:00
|
|
|
go func() {
|
|
|
|
for {
|
2018-05-05 13:26:16 +00:00
|
|
|
ev := <-evQueue
|
|
|
|
if ev.Type == termbox.EventKey && ev.Key == termbox.KeyEsc {
|
|
|
|
select {
|
|
|
|
case ev2 := <-evQueue:
|
|
|
|
ev2.Mod = termbox.ModAlt
|
|
|
|
evChan <- ev2
|
|
|
|
continue
|
|
|
|
case <-time.After(100 * time.Millisecond):
|
|
|
|
}
|
|
|
|
}
|
|
|
|
evChan <- ev
|
2016-12-15 09:26:06 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
return &ui{
|
2018-02-10 17:56:59 +00:00
|
|
|
wins: getWins(),
|
|
|
|
promptWin: newWin(wtot, 1, 0, 0),
|
|
|
|
msgWin: newWin(wtot, 1, 0, htot-1),
|
|
|
|
menuWin: newWin(wtot, 1, 0, htot-2),
|
|
|
|
keyChan: make(chan string, 1000),
|
|
|
|
evChan: evChan,
|
2018-04-14 18:18:39 +00:00
|
|
|
colors: parseColors(),
|
2019-06-28 15:08:11 +00:00
|
|
|
icons: parseIcons(),
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-16 20:17:11 +00:00
|
|
|
func (ui *ui) renew() {
|
2016-08-13 12:49:04 +00:00
|
|
|
wtot, htot := termbox.Size()
|
|
|
|
|
|
|
|
widths := getWidths(wtot)
|
|
|
|
|
|
|
|
wacc := 0
|
|
|
|
wlen := len(widths)
|
|
|
|
for i := 0; i < wlen; i++ {
|
2018-04-15 16:26:51 +00:00
|
|
|
if gOpts.drawbox {
|
|
|
|
ui.wins[i].renew(widths[i], htot-4, wacc+1, 2)
|
|
|
|
} else {
|
|
|
|
ui.wins[i].renew(widths[i], htot-2, wacc, 1)
|
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
wacc += widths[i]
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:18:39 +00:00
|
|
|
ui.promptWin.renew(wtot, 1, 0, 0)
|
2017-11-19 18:55:13 +00:00
|
|
|
ui.msgWin.renew(wtot, 1, 0, htot-1)
|
2018-04-15 15:18:39 +00:00
|
|
|
ui.menuWin.renew(wtot, 1, 0, htot-2)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2018-04-18 18:52:45 +00:00
|
|
|
func (ui *ui) sort() {
|
2018-04-20 19:19:37 +00:00
|
|
|
if ui.dirPrev == nil {
|
|
|
|
return
|
|
|
|
}
|
2018-04-18 18:52:45 +00:00
|
|
|
name := ui.dirPrev.name()
|
|
|
|
ui.dirPrev.sort()
|
2018-08-22 17:05:22 +00:00
|
|
|
ui.dirPrev.sel(name, ui.wins[0].h)
|
2018-04-18 18:52:45 +00:00
|
|
|
}
|
|
|
|
|
2019-02-28 18:58:14 +00:00
|
|
|
func (ui *ui) echo(msg string) {
|
|
|
|
ui.msg = msg
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ui *ui) echof(format string, a ...interface{}) {
|
|
|
|
ui.echo(fmt.Sprintf(format, a...))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ui *ui) echomsg(msg string) {
|
2017-11-19 18:55:13 +00:00
|
|
|
ui.msg = msg
|
|
|
|
log.Print(msg)
|
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2019-02-28 18:58:14 +00:00
|
|
|
func (ui *ui) echomsgf(format string, a ...interface{}) {
|
|
|
|
ui.echomsg(fmt.Sprintf(format, a...))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ui *ui) echoerr(msg string) {
|
2019-03-27 19:07:41 +00:00
|
|
|
ui.msg = fmt.Sprintf(gOpts.errorfmt, msg)
|
2019-02-28 18:58:14 +00:00
|
|
|
log.Printf("error: %s", msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ui *ui) echoerrf(format string, a ...interface{}) {
|
|
|
|
ui.echoerr(fmt.Sprintf(format, a...))
|
2016-12-02 22:05:49 +00:00
|
|
|
}
|
|
|
|
|
2018-02-10 15:59:19 +00:00
|
|
|
type reg struct {
|
2018-06-07 19:49:53 +00:00
|
|
|
loading bool
|
|
|
|
loadTime time.Time
|
|
|
|
path string
|
|
|
|
lines []string
|
2018-01-28 17:13:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ui *ui) loadFile(nav *nav) {
|
|
|
|
curr, err := nav.currFile()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !gOpts.preview {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if curr.IsDir() {
|
2018-02-10 15:59:19 +00:00
|
|
|
ui.dirPrev = nav.loadDir(curr.path)
|
2018-01-28 17:13:28 +00:00
|
|
|
} else if curr.Mode().IsRegular() {
|
2019-08-12 11:52:27 +00:00
|
|
|
ui.regPrev = nav.loadReg(curr.path)
|
2016-09-15 13:16:03 +00:00
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2017-11-19 18:55:13 +00:00
|
|
|
func (ui *ui) loadFileInfo(nav *nav) {
|
|
|
|
curr, err := nav.currFile()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-06-08 23:38:46 +00:00
|
|
|
var linkTarget string
|
|
|
|
if curr.linkTarget != "" {
|
|
|
|
linkTarget = " -> " + curr.linkTarget
|
|
|
|
}
|
|
|
|
ui.echof("%v %4s %v%s", curr.Mode(), humanize(curr.Size()), curr.ModTime().Format(gOpts.timefmt), linkTarget)
|
2017-11-19 18:55:13 +00:00
|
|
|
}
|
|
|
|
|
2018-02-10 17:56:59 +00:00
|
|
|
func (ui *ui) drawPromptLine(nav *nav) {
|
|
|
|
fg, bg := termbox.ColorDefault, termbox.ColorDefault
|
|
|
|
|
2019-10-07 19:30:52 +00:00
|
|
|
pwd := nav.currDir().path
|
2020-07-19 21:19:42 +00:00
|
|
|
|
|
|
|
if strings.HasPrefix(pwd, gUser.HomeDir) {
|
|
|
|
pwd = filepath.Join("~", strings.TrimPrefix(pwd, gUser.HomeDir))
|
2019-10-07 19:30:52 +00:00
|
|
|
}
|
2020-07-19 21:19:42 +00:00
|
|
|
|
|
|
|
sep := string(filepath.Separator)
|
|
|
|
|
|
|
|
if !strings.HasSuffix(pwd, sep) {
|
|
|
|
pwd += sep
|
|
|
|
}
|
|
|
|
|
2018-02-22 15:18:30 +00:00
|
|
|
var fname string
|
2018-02-10 17:56:59 +00:00
|
|
|
curr, err := nav.currFile()
|
|
|
|
if err == nil {
|
2018-02-22 15:18:30 +00:00
|
|
|
fname = filepath.Base(curr.path)
|
2018-02-10 17:56:59 +00:00
|
|
|
}
|
|
|
|
|
2018-02-22 15:18:30 +00:00
|
|
|
var prompt string
|
|
|
|
|
|
|
|
prompt = strings.Replace(gOpts.promptfmt, "%u", gUser.Username, -1)
|
|
|
|
prompt = strings.Replace(prompt, "%h", gHostname, -1)
|
|
|
|
prompt = strings.Replace(prompt, "%f", fname, -1)
|
|
|
|
|
|
|
|
if printLength(strings.Replace(prompt, "%w", pwd, -1)) > ui.promptWin.w {
|
2018-02-10 17:56:59 +00:00
|
|
|
names := strings.Split(pwd, sep)
|
2018-03-22 14:54:24 +00:00
|
|
|
for i := range names {
|
2020-07-19 21:19:42 +00:00
|
|
|
if names[i] == "" {
|
|
|
|
continue
|
|
|
|
}
|
2018-02-10 17:56:59 +00:00
|
|
|
r, _ := utf8.DecodeRuneInString(names[i])
|
|
|
|
names[i] = string(r)
|
2018-02-22 15:18:30 +00:00
|
|
|
if printLength(strings.Replace(prompt, "%w", strings.Join(names, sep), -1)) <= ui.promptWin.w {
|
2018-02-10 17:56:59 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pwd = strings.Join(names, sep)
|
|
|
|
}
|
|
|
|
|
2018-02-22 15:18:30 +00:00
|
|
|
prompt = strings.Replace(prompt, "%w", pwd, -1)
|
|
|
|
|
|
|
|
ui.promptWin.print(0, 0, fg, bg, prompt)
|
2018-02-10 17:56:59 +00:00
|
|
|
}
|
|
|
|
|
2018-01-11 17:46:07 +00:00
|
|
|
func (ui *ui) drawStatLine(nav *nav) {
|
|
|
|
fg, bg := termbox.ColorDefault, termbox.ColorDefault
|
|
|
|
|
2020-07-19 23:47:33 +00:00
|
|
|
dir := nav.currDir()
|
2018-01-11 17:46:07 +00:00
|
|
|
|
|
|
|
ui.msgWin.print(0, 0, fg, bg, ui.msg)
|
|
|
|
|
2020-07-19 23:47:33 +00:00
|
|
|
tot := len(dir.files)
|
|
|
|
ind := min(dir.ind+1, tot)
|
2018-04-12 16:14:50 +00:00
|
|
|
acc := string(ui.keyCount) + string(ui.keyAcc)
|
2018-01-11 17:46:07 +00:00
|
|
|
|
2019-02-28 18:04:38 +00:00
|
|
|
var progress string
|
2019-03-01 14:44:50 +00:00
|
|
|
|
2019-02-28 18:04:38 +00:00
|
|
|
if nav.copyTotal > 0 {
|
|
|
|
percentage := int((100 * float64(nav.copyBytes)) / float64(nav.copyTotal))
|
2019-03-01 14:44:50 +00:00
|
|
|
progress += fmt.Sprintf(" [%d%%]", percentage)
|
|
|
|
}
|
|
|
|
|
|
|
|
if nav.moveTotal > 0 {
|
|
|
|
progress += fmt.Sprintf(" [%d/%d]", nav.moveCount, nav.moveTotal)
|
2019-02-28 18:04:38 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 16:50:13 +00:00
|
|
|
if nav.deleteTotal > 0 {
|
|
|
|
progress += fmt.Sprintf(" [%d/%d]", nav.deleteCount, nav.deleteTotal)
|
|
|
|
}
|
|
|
|
|
2019-02-28 18:04:38 +00:00
|
|
|
ruler := fmt.Sprintf("%s%s %d/%d", acc, progress, ind, tot)
|
2018-01-11 17:46:07 +00:00
|
|
|
|
|
|
|
ui.msgWin.printRight(0, fg, bg, ruler)
|
|
|
|
}
|
|
|
|
|
2019-08-12 11:52:27 +00:00
|
|
|
func (ui *ui) drawBox() {
|
2018-04-15 16:26:51 +00:00
|
|
|
fg, bg := termbox.ColorDefault, termbox.ColorDefault
|
|
|
|
|
|
|
|
w, h := termbox.Size()
|
|
|
|
|
|
|
|
for i := 1; i < w-1; i++ {
|
|
|
|
termbox.SetCell(i, 1, '─', fg, bg)
|
|
|
|
termbox.SetCell(i, h-2, '─', fg, bg)
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 2; i < h-2; i++ {
|
|
|
|
termbox.SetCell(0, i, '│', fg, bg)
|
|
|
|
termbox.SetCell(w-1, i, '│', fg, bg)
|
|
|
|
}
|
|
|
|
|
|
|
|
termbox.SetCell(0, 1, '┌', fg, bg)
|
|
|
|
termbox.SetCell(w-1, 1, '┐', fg, bg)
|
|
|
|
termbox.SetCell(0, h-2, '└', fg, bg)
|
|
|
|
termbox.SetCell(w-1, h-2, '┘', fg, bg)
|
|
|
|
|
|
|
|
wacc := 0
|
|
|
|
for wind := 0; wind < len(ui.wins)-1; wind++ {
|
|
|
|
wacc += ui.wins[wind].w
|
|
|
|
termbox.SetCell(wacc, 1, '┬', fg, bg)
|
|
|
|
for i := 2; i < h-2; i++ {
|
|
|
|
termbox.SetCell(wacc, i, '│', fg, bg)
|
|
|
|
}
|
|
|
|
termbox.SetCell(wacc, h-2, '┴', fg, bg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (ui *ui) draw(nav *nav) {
|
2016-08-13 12:49:04 +00:00
|
|
|
fg, bg := termbox.ColorDefault, termbox.ColorDefault
|
|
|
|
|
|
|
|
termbox.Clear(fg, bg)
|
2016-10-08 11:18:26 +00:00
|
|
|
|
2018-02-10 17:56:59 +00:00
|
|
|
ui.drawPromptLine(nav)
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-12-15 09:26:06 +00:00
|
|
|
length := min(len(ui.wins), len(nav.dirs))
|
|
|
|
woff := len(ui.wins) - length
|
2016-08-13 12:49:04 +00:00
|
|
|
|
|
|
|
if gOpts.preview {
|
|
|
|
length = min(len(ui.wins)-1, len(nav.dirs))
|
|
|
|
woff = len(ui.wins) - 1 - length
|
|
|
|
}
|
|
|
|
|
2016-12-15 09:26:06 +00:00
|
|
|
doff := len(nav.dirs) - length
|
2016-08-13 12:49:04 +00:00
|
|
|
for i := 0; i < length; i++ {
|
2019-06-28 15:08:11 +00:00
|
|
|
ui.wins[woff+i].printDir(nav.dirs[doff+i], nav.selections, nav.saves, ui.colors, ui.icons)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2018-04-03 19:22:58 +00:00
|
|
|
switch ui.cmdPrefix {
|
|
|
|
case "":
|
|
|
|
ui.drawStatLine(nav)
|
|
|
|
termbox.HideCursor()
|
|
|
|
case ">":
|
|
|
|
ui.msgWin.printLine(0, 0, fg, bg, ui.cmdPrefix)
|
|
|
|
ui.msgWin.print(len(ui.cmdPrefix), 0, fg, bg, ui.msg)
|
|
|
|
ui.msgWin.print(len(ui.cmdPrefix)+len(ui.msg), 0, fg, bg, string(ui.cmdAccLeft))
|
|
|
|
ui.msgWin.print(len(ui.cmdPrefix)+len(ui.msg)+runeSliceWidth(ui.cmdAccLeft), 0, fg, bg, string(ui.cmdAccRight))
|
|
|
|
termbox.SetCursor(ui.msgWin.x+len(ui.cmdPrefix)+len(ui.msg)+runeSliceWidth(ui.cmdAccLeft), ui.msgWin.y)
|
|
|
|
default:
|
2018-01-11 17:46:07 +00:00
|
|
|
ui.msgWin.printLine(0, 0, fg, bg, ui.cmdPrefix)
|
2017-11-19 18:55:13 +00:00
|
|
|
ui.msgWin.print(len(ui.cmdPrefix), 0, fg, bg, string(ui.cmdAccLeft))
|
|
|
|
ui.msgWin.print(len(ui.cmdPrefix)+runeSliceWidth(ui.cmdAccLeft), 0, fg, bg, string(ui.cmdAccRight))
|
|
|
|
termbox.SetCursor(ui.msgWin.x+len(ui.cmdPrefix)+runeSliceWidth(ui.cmdAccLeft), ui.msgWin.y)
|
2016-12-15 09:26:06 +00:00
|
|
|
}
|
2016-08-14 12:15:54 +00:00
|
|
|
|
2016-08-13 12:49:04 +00:00
|
|
|
if gOpts.preview {
|
2020-07-19 23:47:33 +00:00
|
|
|
curr, err := nav.currFile()
|
2016-12-15 13:43:29 +00:00
|
|
|
if err == nil {
|
|
|
|
preview := ui.wins[len(ui.wins)-1]
|
2016-08-14 12:15:54 +00:00
|
|
|
|
2020-07-19 23:47:33 +00:00
|
|
|
if curr.IsDir() {
|
2019-06-28 15:08:11 +00:00
|
|
|
preview.printDir(ui.dirPrev, nav.selections, nav.saves, ui.colors, ui.icons)
|
2020-07-19 23:47:33 +00:00
|
|
|
} else if curr.Mode().IsRegular() {
|
2017-11-19 18:55:13 +00:00
|
|
|
preview.printReg(ui.regPrev)
|
2016-12-15 13:43:29 +00:00
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
}
|
2016-12-15 09:26:06 +00:00
|
|
|
|
2018-04-15 16:26:51 +00:00
|
|
|
if gOpts.drawbox {
|
2019-08-12 11:52:27 +00:00
|
|
|
ui.drawBox()
|
2018-04-15 16:26:51 +00:00
|
|
|
}
|
|
|
|
|
2017-11-19 18:55:13 +00:00
|
|
|
if ui.menuBuf != nil {
|
|
|
|
lines := strings.Split(ui.menuBuf.String(), "\n")
|
2016-12-15 09:26:06 +00:00
|
|
|
|
|
|
|
lines = lines[:len(lines)-1]
|
|
|
|
|
2017-11-19 18:55:13 +00:00
|
|
|
ui.menuWin.h = len(lines) - 1
|
|
|
|
ui.menuWin.y = ui.wins[0].h - ui.menuWin.h
|
2016-12-15 09:26:06 +00:00
|
|
|
|
2018-04-15 16:26:51 +00:00
|
|
|
if gOpts.drawbox {
|
|
|
|
ui.menuWin.y += 2
|
|
|
|
}
|
|
|
|
|
2018-01-11 17:46:07 +00:00
|
|
|
ui.menuWin.printLine(0, 0, termbox.AttrBold, termbox.AttrBold, lines[0])
|
2016-12-15 09:26:06 +00:00
|
|
|
for i, line := range lines[1:] {
|
2018-01-11 17:46:07 +00:00
|
|
|
ui.menuWin.printLine(0, i+1, fg, bg, "")
|
2017-11-19 18:55:13 +00:00
|
|
|
ui.menuWin.print(0, i+1, fg, bg, line)
|
2016-12-15 09:26:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
termbox.Flush()
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func findBinds(keys map[string]expr, prefix string) (binds map[string]expr, ok bool) {
|
|
|
|
binds = make(map[string]expr)
|
2016-08-13 12:49:04 +00:00
|
|
|
for key, expr := range keys {
|
2018-05-20 17:30:41 +00:00
|
|
|
if !strings.HasPrefix(key, prefix) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
binds[key] = expr
|
|
|
|
if key == prefix {
|
|
|
|
ok = true
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func listBinds(binds map[string]expr) *bytes.Buffer {
|
2016-12-15 09:26:06 +00:00
|
|
|
t := new(tabwriter.Writer)
|
|
|
|
b := new(bytes.Buffer)
|
|
|
|
|
|
|
|
var keys []string
|
|
|
|
for k := range binds {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
t.Init(b, 0, gOpts.tabstop, 2, '\t', 0)
|
|
|
|
fmt.Fprintln(t, "keys\tcommand")
|
|
|
|
for _, k := range keys {
|
|
|
|
fmt.Fprintf(t, "%s\t%v\n", k, binds[k])
|
|
|
|
}
|
|
|
|
t.Flush()
|
|
|
|
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2018-07-09 18:35:04 +00:00
|
|
|
func listMarks(marks map[string]string) *bytes.Buffer {
|
|
|
|
t := new(tabwriter.Writer)
|
|
|
|
b := new(bytes.Buffer)
|
|
|
|
|
|
|
|
var keys []string
|
|
|
|
for k := range marks {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
t.Init(b, 0, gOpts.tabstop, 2, '\t', 0)
|
|
|
|
fmt.Fprintln(t, "mark\tpath")
|
|
|
|
for _, k := range keys {
|
|
|
|
fmt.Fprintf(t, "%s\t%s\n", k, marks[k])
|
|
|
|
}
|
|
|
|
t.Flush()
|
|
|
|
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (ui *ui) pollEvent() termbox.Event {
|
2016-12-15 09:26:06 +00:00
|
|
|
select {
|
2017-11-19 18:55:13 +00:00
|
|
|
case key := <-ui.keyChan:
|
2016-09-18 16:21:24 +00:00
|
|
|
ev := termbox.Event{Type: termbox.EventKey}
|
2016-12-15 09:26:06 +00:00
|
|
|
|
2020-07-19 22:36:22 +00:00
|
|
|
if utf8.RuneCountInString(key) == 1 {
|
2016-12-15 09:26:06 +00:00
|
|
|
ev.Ch, _ = utf8.DecodeRuneInString(key)
|
2016-09-18 16:21:24 +00:00
|
|
|
} else {
|
2018-05-05 13:26:16 +00:00
|
|
|
switch {
|
|
|
|
case key == "<lt>":
|
2016-09-18 16:21:24 +00:00
|
|
|
ev.Ch = '<'
|
2018-05-05 13:26:16 +00:00
|
|
|
case key == "<gt>":
|
2016-09-18 16:21:24 +00:00
|
|
|
ev.Ch = '>'
|
2018-05-20 17:30:41 +00:00
|
|
|
case reAltKey.MatchString(key):
|
|
|
|
match := reAltKey.FindStringSubmatch(key)[1]
|
2018-05-05 13:26:16 +00:00
|
|
|
ev.Ch, _ = utf8.DecodeRuneInString(match)
|
|
|
|
ev.Mod = termbox.ModAlt
|
2016-09-18 16:21:24 +00:00
|
|
|
default:
|
2016-12-15 09:26:06 +00:00
|
|
|
if val, ok := gValKey[key]; ok {
|
2016-09-18 16:21:24 +00:00
|
|
|
ev.Key = val
|
|
|
|
} else {
|
|
|
|
ev.Key = termbox.KeyEsc
|
2019-02-28 18:58:14 +00:00
|
|
|
ui.echoerrf("unknown key: %s", key)
|
2016-09-18 16:21:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-12-15 09:26:06 +00:00
|
|
|
|
|
|
|
return ev
|
2017-11-19 18:55:13 +00:00
|
|
|
case ev := <-ui.evChan:
|
2016-09-18 16:21:24 +00:00
|
|
|
return ev
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
// This function is used to read a normal event on the client side. For keys,
|
|
|
|
// digits are interpreted as command counts but this is only done for digits
|
|
|
|
// preceding any non-digit characters (e.g. "42y2k" as 42 times "y2k").
|
2018-04-12 15:04:37 +00:00
|
|
|
func (ui *ui) readEvent(ch chan<- expr, ev termbox.Event) {
|
2018-04-12 18:54:40 +00:00
|
|
|
draw := &callExpr{"draw", nil, 1}
|
2016-12-15 09:26:06 +00:00
|
|
|
count := 1
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-12-18 15:01:45 +00:00
|
|
|
switch ev.Type {
|
|
|
|
case termbox.EventKey:
|
|
|
|
if ev.Ch != 0 {
|
|
|
|
switch {
|
|
|
|
case ev.Ch == '<':
|
2018-05-20 17:30:41 +00:00
|
|
|
ui.keyAcc = append(ui.keyAcc, []rune("<lt>")...)
|
2016-12-18 15:01:45 +00:00
|
|
|
case ev.Ch == '>':
|
2018-05-20 17:30:41 +00:00
|
|
|
ui.keyAcc = append(ui.keyAcc, []rune("<gt>")...)
|
2018-05-05 13:26:16 +00:00
|
|
|
case ev.Mod == termbox.ModAlt:
|
|
|
|
ui.keyAcc = append(ui.keyAcc, '<', 'a', '-', ev.Ch, '>')
|
2017-11-19 18:55:13 +00:00
|
|
|
case unicode.IsDigit(ev.Ch) && len(ui.keyAcc) == 0:
|
|
|
|
ui.keyCount = append(ui.keyCount, ev.Ch)
|
2016-12-18 15:01:45 +00:00
|
|
|
default:
|
2017-11-19 18:55:13 +00:00
|
|
|
ui.keyAcc = append(ui.keyAcc, ev.Ch)
|
2016-12-18 15:01:45 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
val := gKeyVal[ev.Key]
|
2018-05-20 17:30:41 +00:00
|
|
|
if val == "<esc>" {
|
2018-04-12 18:54:40 +00:00
|
|
|
ch <- draw
|
2017-11-19 18:55:13 +00:00
|
|
|
ui.keyAcc = nil
|
|
|
|
ui.keyCount = nil
|
2020-07-25 21:24:04 +00:00
|
|
|
ui.menuBuf = nil
|
|
|
|
return
|
2016-12-15 09:26:06 +00:00
|
|
|
}
|
2018-05-20 17:30:41 +00:00
|
|
|
ui.keyAcc = append(ui.keyAcc, []rune(val)...)
|
2016-12-18 15:01:45 +00:00
|
|
|
}
|
2016-12-15 09:26:06 +00:00
|
|
|
|
2018-04-12 16:14:50 +00:00
|
|
|
if len(ui.keyAcc) == 0 {
|
2018-04-12 18:54:40 +00:00
|
|
|
ch <- draw
|
2018-04-12 16:14:50 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2017-11-19 18:55:13 +00:00
|
|
|
binds, ok := findBinds(gOpts.keys, string(ui.keyAcc))
|
2016-12-18 15:01:45 +00:00
|
|
|
|
|
|
|
switch len(binds) {
|
|
|
|
case 0:
|
2019-02-28 18:58:14 +00:00
|
|
|
ui.echoerrf("unknown mapping: %s", string(ui.keyAcc))
|
2018-04-12 18:54:40 +00:00
|
|
|
ch <- draw
|
2017-11-19 18:55:13 +00:00
|
|
|
ui.keyAcc = nil
|
|
|
|
ui.keyCount = nil
|
2018-01-11 16:44:02 +00:00
|
|
|
ui.menuBuf = nil
|
2016-12-18 15:01:45 +00:00
|
|
|
default:
|
|
|
|
if ok {
|
2017-11-19 18:55:13 +00:00
|
|
|
if len(ui.keyCount) > 0 {
|
|
|
|
c, err := strconv.Atoi(string(ui.keyCount))
|
2016-12-18 15:01:45 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("converting command count: %s", err)
|
2016-09-07 19:34:29 +00:00
|
|
|
}
|
2016-12-18 15:01:45 +00:00
|
|
|
count = c
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
2017-11-19 18:55:13 +00:00
|
|
|
expr := gOpts.keys[string(ui.keyAcc)]
|
2018-04-12 15:04:37 +00:00
|
|
|
if e, ok := expr.(*callExpr); ok {
|
|
|
|
e.count = count
|
2020-07-03 15:29:55 +00:00
|
|
|
} else if e, ok := expr.(*listExpr); ok {
|
|
|
|
e.count = count
|
2018-04-12 15:04:37 +00:00
|
|
|
}
|
|
|
|
ch <- expr
|
2017-11-19 18:55:13 +00:00
|
|
|
ui.keyAcc = nil
|
|
|
|
ui.keyCount = nil
|
2018-04-12 16:14:50 +00:00
|
|
|
ui.menuBuf = nil
|
|
|
|
} else {
|
2017-11-19 18:55:13 +00:00
|
|
|
ui.menuBuf = listBinds(binds)
|
2018-04-12 18:54:40 +00:00
|
|
|
ch <- draw
|
2016-12-18 15:01:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
case termbox.EventResize:
|
2018-04-12 19:56:09 +00:00
|
|
|
ch <- &callExpr{"redraw", nil, 1}
|
2016-12-18 15:01:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
func readCmdEvent(ch chan<- expr, ev termbox.Event) {
|
|
|
|
if ev.Ch != 0 {
|
|
|
|
if ev.Mod == termbox.ModAlt {
|
|
|
|
val := string([]rune{'<', 'a', '-', ev.Ch, '>'})
|
|
|
|
if expr, ok := gOpts.cmdkeys[val]; ok {
|
|
|
|
ch <- expr
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ch <- &callExpr{"cmd-insert", []string{string(ev.Ch)}, 1}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
val := gKeyVal[ev.Key]
|
|
|
|
if expr, ok := gOpts.cmdkeys[val]; ok {
|
|
|
|
ch <- expr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-12 15:04:37 +00:00
|
|
|
func (ui *ui) readExpr() <-chan expr {
|
|
|
|
ch := make(chan expr)
|
2016-12-18 15:01:45 +00:00
|
|
|
|
2018-04-05 20:06:52 +00:00
|
|
|
ui.exprChan = ch
|
|
|
|
|
2016-12-18 15:01:45 +00:00
|
|
|
go func() {
|
2018-04-12 18:54:40 +00:00
|
|
|
ch <- &callExpr{"draw", nil, 1}
|
2017-11-19 18:55:13 +00:00
|
|
|
|
2016-12-18 15:01:45 +00:00
|
|
|
for {
|
|
|
|
ev := ui.pollEvent()
|
|
|
|
|
2017-11-19 18:55:13 +00:00
|
|
|
if ui.cmdPrefix != "" && ev.Type == termbox.EventKey {
|
2016-12-18 15:01:45 +00:00
|
|
|
readCmdEvent(ch, ev)
|
|
|
|
continue
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
2016-12-18 15:01:45 +00:00
|
|
|
|
|
|
|
ui.readEvent(ch, ev)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
2016-12-15 09:26:06 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
return ch
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2019-01-08 19:47:41 +00:00
|
|
|
func setColorMode() {
|
|
|
|
if gOpts.color256 {
|
|
|
|
termbox.SetOutputMode(termbox.Output256)
|
|
|
|
} else {
|
|
|
|
termbox.SetOutputMode(termbox.OutputNormal)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (ui *ui) pause() {
|
2016-08-13 12:49:04 +00:00
|
|
|
termbox.Close()
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (ui *ui) resume() {
|
2016-08-13 12:49:04 +00:00
|
|
|
if err := termbox.Init(); err != nil {
|
2016-08-17 20:22:11 +00:00
|
|
|
log.Fatalf("initializing termbox: %s", err)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (ui *ui) sync() {
|
2016-08-17 20:28:42 +00:00
|
|
|
if err := termbox.Sync(); err != nil {
|
2016-08-17 20:22:11 +00:00
|
|
|
log.Printf("syncing termbox: %s", err)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-15 09:26:06 +00:00
|
|
|
func listMatches(matches []string) *bytes.Buffer {
|
2016-08-21 15:41:03 +00:00
|
|
|
b := new(bytes.Buffer)
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-08-21 15:41:03 +00:00
|
|
|
wtot, _ := termbox.Size()
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-08-21 15:41:03 +00:00
|
|
|
wcol := 0
|
|
|
|
for _, m := range matches {
|
|
|
|
wcol = max(wcol, len(m))
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
2016-08-21 15:41:03 +00:00
|
|
|
wcol += gOpts.tabstop - wcol%gOpts.tabstop
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-08-21 15:41:03 +00:00
|
|
|
ncol := wtot / wcol
|
|
|
|
|
|
|
|
b.WriteString("possible matches\n")
|
|
|
|
for i := 0; i < len(matches); i++ {
|
|
|
|
for j := 0; j < ncol && i < len(matches); i, j = i+1, j+1 {
|
|
|
|
b.WriteString(fmt.Sprintf("%s%*s", matches[i], wcol-len(matches[i]), ""))
|
|
|
|
}
|
|
|
|
b.WriteByte('\n')
|
|
|
|
}
|
|
|
|
|
2016-12-15 09:26:06 +00:00
|
|
|
return b
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|