2016-08-13 12:49:04 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2016-09-06 20:05:18 +00:00
|
|
|
"path/filepath"
|
2016-08-13 12:49:04 +00:00
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Dir struct {
|
|
|
|
ind int // which entry is highlighted
|
|
|
|
pos int // which line in the ui highlighted entry is
|
|
|
|
path string
|
|
|
|
fi []os.FileInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
type ByName []os.FileInfo
|
|
|
|
|
|
|
|
func (a ByName) Len() int { return len(a) }
|
|
|
|
func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
|
|
|
|
func (a ByName) Less(i, j int) bool {
|
|
|
|
return strings.ToLower(a[i].Name()) < strings.ToLower(a[j].Name())
|
|
|
|
}
|
|
|
|
|
|
|
|
type BySize []os.FileInfo
|
|
|
|
|
|
|
|
func (a BySize) Len() int { return len(a) }
|
|
|
|
func (a BySize) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
func (a BySize) Less(i, j int) bool { return a[i].Size() < a[j].Size() }
|
|
|
|
|
|
|
|
type ByTime []os.FileInfo
|
|
|
|
|
|
|
|
func (a ByTime) Len() int { return len(a) }
|
|
|
|
func (a ByTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
func (a ByTime) Less(i, j int) bool { return a[i].ModTime().Before(a[j].ModTime()) }
|
|
|
|
|
|
|
|
type ByDir []os.FileInfo
|
|
|
|
|
|
|
|
func (a ByDir) Len() int { return len(a) }
|
|
|
|
func (a ByDir) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
|
|
|
|
func (a ByDir) Less(i, j int) bool {
|
|
|
|
if a[i].IsDir() == a[j].IsDir() {
|
|
|
|
return i < j
|
|
|
|
}
|
|
|
|
return a[i].IsDir()
|
|
|
|
}
|
|
|
|
|
|
|
|
type ByNum []os.FileInfo
|
|
|
|
|
|
|
|
func (a ByNum) Len() int { return len(a) }
|
|
|
|
func (a ByNum) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
|
|
|
|
func (a ByNum) Less(i, j int) bool {
|
|
|
|
nums1, rest1, numFirst1 := extractNums(a[i].Name())
|
|
|
|
nums2, rest2, numFirst2 := extractNums(a[j].Name())
|
|
|
|
|
|
|
|
if numFirst1 != numFirst2 {
|
|
|
|
return i < j
|
|
|
|
}
|
|
|
|
|
|
|
|
if numFirst1 {
|
|
|
|
if nums1[0] != nums2[0] {
|
|
|
|
return nums1[0] < nums2[0]
|
|
|
|
}
|
|
|
|
nums1 = nums1[1:]
|
|
|
|
nums2 = nums2[1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
for k := 0; k < len(nums1) && k < len(nums2); k++ {
|
|
|
|
if rest1[k] != rest2[k] {
|
|
|
|
return i < j
|
|
|
|
}
|
|
|
|
if nums1[k] != nums2[k] {
|
|
|
|
return nums1[k] < nums2[k]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return i < j
|
|
|
|
}
|
|
|
|
|
|
|
|
func organizeFiles(fi []os.FileInfo) []os.FileInfo {
|
|
|
|
if !gOpts.hidden {
|
|
|
|
var tmp []os.FileInfo
|
|
|
|
for _, f := range fi {
|
|
|
|
if f.Name()[0] != '.' {
|
|
|
|
tmp = append(tmp, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fi = tmp
|
|
|
|
}
|
|
|
|
|
|
|
|
switch gOpts.sortby {
|
|
|
|
case "name":
|
|
|
|
sort.Sort(ByName(fi))
|
|
|
|
case "size":
|
|
|
|
sort.Sort(BySize(fi))
|
|
|
|
case "time":
|
|
|
|
sort.Sort(ByTime(fi))
|
|
|
|
default:
|
|
|
|
log.Printf("unknown sorting type: %s", gOpts.sortby)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: these should be optional
|
|
|
|
sort.Stable(ByNum(fi))
|
|
|
|
sort.Stable(ByDir(fi))
|
|
|
|
|
|
|
|
return fi
|
|
|
|
}
|
|
|
|
|
|
|
|
func newDir(path string) *Dir {
|
|
|
|
fi, err := ioutil.ReadDir(path)
|
|
|
|
if err != nil {
|
2016-08-17 20:22:11 +00:00
|
|
|
log.Printf("reading directory: %s", err)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fi = organizeFiles(fi)
|
|
|
|
|
|
|
|
return &Dir{
|
|
|
|
path: path,
|
|
|
|
fi: fi,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dir *Dir) renew(height int) {
|
|
|
|
fi, err := ioutil.ReadDir(dir.path)
|
|
|
|
if err != nil {
|
2016-10-01 22:14:55 +00:00
|
|
|
log.Printf("reading directory: %s", err)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fi = organizeFiles(fi)
|
|
|
|
|
|
|
|
var name string
|
|
|
|
if len(dir.fi) != 0 {
|
|
|
|
name = dir.fi[dir.ind].Name()
|
|
|
|
}
|
|
|
|
|
|
|
|
dir.fi = fi
|
|
|
|
|
|
|
|
dir.load(dir.ind, dir.pos, height, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dir *Dir) load(ind, pos, height int, name string) {
|
|
|
|
if len(dir.fi) == 0 {
|
|
|
|
dir.ind, dir.pos = 0, 0
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ind = max(0, min(ind, len(dir.fi)-1))
|
|
|
|
|
|
|
|
if dir.fi[ind].Name() != name {
|
|
|
|
for i, f := range dir.fi {
|
|
|
|
if f.Name() == name {
|
|
|
|
ind = i
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
edge := min(gOpts.scrolloff, len(dir.fi)-ind-1)
|
|
|
|
pos = min(ind, height-edge-1)
|
|
|
|
}
|
|
|
|
|
|
|
|
dir.ind = ind
|
|
|
|
dir.pos = pos
|
|
|
|
}
|
|
|
|
|
|
|
|
type Nav struct {
|
|
|
|
dirs []*Dir
|
|
|
|
inds map[string]int
|
|
|
|
poss map[string]int
|
|
|
|
names map[string]string
|
|
|
|
marks map[string]bool
|
|
|
|
height int
|
|
|
|
}
|
|
|
|
|
|
|
|
func getDirs(wd string, height int) []*Dir {
|
|
|
|
var dirs []*Dir
|
|
|
|
|
2016-09-06 20:05:18 +00:00
|
|
|
for curr, base := wd, ""; !isRoot(base); curr, base = filepath.Dir(curr), filepath.Base(curr) {
|
2016-08-13 12:49:04 +00:00
|
|
|
dir := newDir(curr)
|
|
|
|
for i, f := range dir.fi {
|
|
|
|
if f.Name() == base {
|
|
|
|
dir.ind = i
|
|
|
|
edge := min(gOpts.scrolloff, len(dir.fi)-dir.ind-1)
|
|
|
|
dir.pos = min(i, height-edge-1)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dirs = append(dirs, dir)
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, j := 0, len(dirs)-1; i < j; i, j = i+1, j-1 {
|
|
|
|
dirs[i], dirs[j] = dirs[j], dirs[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
return dirs
|
|
|
|
}
|
|
|
|
|
|
|
|
func newNav(height int) *Nav {
|
|
|
|
wd, err := os.Getwd()
|
|
|
|
if err != nil {
|
2016-08-17 20:22:11 +00:00
|
|
|
log.Printf("getting current directory: %s", err)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dirs := getDirs(wd, height)
|
|
|
|
|
|
|
|
return &Nav{
|
|
|
|
dirs: dirs,
|
|
|
|
inds: make(map[string]int),
|
|
|
|
poss: make(map[string]int),
|
|
|
|
names: make(map[string]string),
|
|
|
|
marks: make(map[string]bool),
|
|
|
|
height: height,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) renew(height int) {
|
|
|
|
nav.height = height
|
|
|
|
for _, d := range nav.dirs {
|
|
|
|
d.renew(nav.height)
|
|
|
|
}
|
|
|
|
|
|
|
|
for m := range nav.marks {
|
|
|
|
if _, err := os.Stat(m); os.IsNotExist(err) {
|
|
|
|
delete(nav.marks, m)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-08 21:04:44 +00:00
|
|
|
func (nav *Nav) up(dist int) {
|
2016-08-14 15:39:02 +00:00
|
|
|
dir := nav.currDir()
|
|
|
|
|
|
|
|
if dir.ind == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-09-08 21:04:44 +00:00
|
|
|
dir.ind -= dist
|
|
|
|
dir.ind = max(0, dir.ind)
|
2016-08-14 15:39:02 +00:00
|
|
|
|
2016-09-08 21:04:44 +00:00
|
|
|
dir.pos -= dist
|
2016-08-14 15:39:02 +00:00
|
|
|
edge := min(gOpts.scrolloff, dir.ind)
|
|
|
|
dir.pos = max(dir.pos, edge)
|
|
|
|
}
|
|
|
|
|
2016-09-08 21:04:44 +00:00
|
|
|
func (nav *Nav) down(dist int) {
|
2016-08-13 12:49:04 +00:00
|
|
|
dir := nav.currDir()
|
|
|
|
|
|
|
|
maxind := len(dir.fi) - 1
|
|
|
|
|
|
|
|
if dir.ind >= maxind {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-09-08 21:04:44 +00:00
|
|
|
dir.ind += dist
|
|
|
|
dir.ind = min(maxind, dir.ind)
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-09-08 21:04:44 +00:00
|
|
|
dir.pos += dist
|
2016-08-13 12:49:04 +00:00
|
|
|
edge := min(gOpts.scrolloff, maxind-dir.ind)
|
2016-09-02 19:47:11 +00:00
|
|
|
|
|
|
|
// use a smaller value when the height is even and scrolloff is maxed
|
|
|
|
// in order to stay at the same row as much as possible while up/down
|
2016-09-02 20:06:25 +00:00
|
|
|
edge = min(edge, nav.height/2+nav.height%2-1)
|
2016-09-02 19:47:11 +00:00
|
|
|
|
2016-08-13 12:49:04 +00:00
|
|
|
dir.pos = min(dir.pos, nav.height-edge-1)
|
|
|
|
dir.pos = min(dir.pos, maxind)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) updir() error {
|
|
|
|
if len(nav.dirs) <= 1 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
dir := nav.currDir()
|
|
|
|
|
|
|
|
nav.inds[dir.path] = dir.ind
|
|
|
|
nav.poss[dir.path] = dir.pos
|
|
|
|
|
|
|
|
if len(dir.fi) != 0 {
|
|
|
|
nav.names[dir.path] = dir.fi[dir.ind].Name()
|
|
|
|
}
|
|
|
|
|
|
|
|
nav.dirs = nav.dirs[:len(nav.dirs)-1]
|
|
|
|
|
2016-09-06 20:05:18 +00:00
|
|
|
if err := os.Chdir(filepath.Dir(dir.path)); err != nil {
|
2016-08-13 12:49:04 +00:00
|
|
|
return fmt.Errorf("updir: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) open() error {
|
|
|
|
path := nav.currPath()
|
|
|
|
|
2016-08-14 12:37:22 +00:00
|
|
|
dir := newDir(path)
|
2016-08-14 12:15:54 +00:00
|
|
|
|
2016-08-14 12:37:22 +00:00
|
|
|
dir.load(nav.inds[path], nav.poss[path], nav.height, nav.names[path])
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-08-14 12:37:22 +00:00
|
|
|
nav.dirs = append(nav.dirs, dir)
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-08-17 20:28:42 +00:00
|
|
|
if err := os.Chdir(path); err != nil {
|
2016-08-14 12:37:22 +00:00
|
|
|
return fmt.Errorf("open: %s", err)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) bot() {
|
|
|
|
dir := nav.currDir()
|
|
|
|
|
|
|
|
dir.ind = len(dir.fi) - 1
|
|
|
|
dir.pos = min(dir.ind, nav.height-1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) top() {
|
|
|
|
dir := nav.currDir()
|
|
|
|
|
|
|
|
dir.ind = 0
|
|
|
|
dir.pos = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) cd(wd string) error {
|
2016-08-14 15:38:33 +00:00
|
|
|
wd = strings.Replace(wd, "~", envHome, -1)
|
2016-10-01 21:39:03 +00:00
|
|
|
wd = filepath.Clean(wd)
|
2016-08-14 15:38:33 +00:00
|
|
|
|
2016-09-06 20:05:18 +00:00
|
|
|
if !filepath.IsAbs(wd) {
|
|
|
|
wd = filepath.Join(nav.currDir().path, wd)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2016-08-17 20:28:42 +00:00
|
|
|
if err := os.Chdir(wd); err != nil {
|
2016-08-13 12:49:04 +00:00
|
|
|
return fmt.Errorf("cd: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
nav.dirs = getDirs(wd, nav.height)
|
|
|
|
|
|
|
|
// TODO: save/load ind and pos from the map
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) toggle() {
|
|
|
|
path := nav.currPath()
|
|
|
|
|
|
|
|
if nav.marks[path] {
|
|
|
|
delete(nav.marks, path)
|
|
|
|
} else {
|
|
|
|
nav.marks[path] = true
|
|
|
|
}
|
|
|
|
|
2016-09-08 21:04:44 +00:00
|
|
|
nav.down(1)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) save(keep bool) error {
|
|
|
|
if len(nav.marks) == 0 {
|
|
|
|
path := nav.currPath()
|
|
|
|
|
2016-08-17 20:28:42 +00:00
|
|
|
if err := saveFiles([]string{path}, keep); err != nil {
|
2016-08-13 12:49:04 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var fs []string
|
|
|
|
for f := range nav.marks {
|
|
|
|
fs = append(fs, f)
|
|
|
|
}
|
|
|
|
|
2016-08-17 20:28:42 +00:00
|
|
|
if err := saveFiles(fs, keep); err != nil {
|
2016-08-13 12:49:04 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) paste() error {
|
|
|
|
list, keep, err := loadFiles()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(list) == 0 {
|
|
|
|
return errors.New("no file in yank/delete buffer")
|
|
|
|
}
|
|
|
|
|
|
|
|
dir := nav.currDir()
|
|
|
|
|
2016-09-13 19:56:03 +00:00
|
|
|
var args []string
|
2016-08-13 12:49:04 +00:00
|
|
|
|
|
|
|
var sh string
|
|
|
|
if keep {
|
|
|
|
sh = "cp"
|
2016-09-13 19:56:03 +00:00
|
|
|
args = append(args, "-r")
|
2016-08-13 12:49:04 +00:00
|
|
|
} else {
|
|
|
|
sh = "mv"
|
|
|
|
}
|
|
|
|
|
2016-09-13 19:56:03 +00:00
|
|
|
args = append(args, "--backup=numbered")
|
|
|
|
args = append(args, list...)
|
|
|
|
args = append(args, dir.path)
|
|
|
|
|
2016-08-13 12:49:04 +00:00
|
|
|
cmd := exec.Command(sh, args...)
|
|
|
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
return fmt.Errorf("%s: %s", sh, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: async?
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) currDir() *Dir {
|
|
|
|
return nav.dirs[len(nav.dirs)-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) currFile() os.FileInfo {
|
|
|
|
last := nav.dirs[len(nav.dirs)-1]
|
|
|
|
return last.fi[last.ind]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) currPath() string {
|
|
|
|
last := nav.dirs[len(nav.dirs)-1]
|
|
|
|
curr := last.fi[last.ind]
|
2016-09-06 20:05:18 +00:00
|
|
|
return filepath.Join(last.path, curr.Name())
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *Nav) currMarks() []string {
|
|
|
|
marks := make([]string, 0, len(nav.marks))
|
|
|
|
for m := range nav.marks {
|
|
|
|
marks = append(marks, m)
|
|
|
|
}
|
|
|
|
return marks
|
|
|
|
}
|