2016-08-13 12:49:04 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2018-02-10 15:59:19 +00:00
|
|
|
"bufio"
|
2016-08-13 12:49:04 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2018-02-10 15:59:19 +00:00
|
|
|
"io"
|
2016-08-13 12:49:04 +00:00
|
|
|
"log"
|
|
|
|
"os"
|
2018-02-10 15:59:19 +00:00
|
|
|
"os/exec"
|
2016-09-06 20:05:18 +00:00
|
|
|
"path/filepath"
|
2016-11-10 21:18:56 +00:00
|
|
|
"sort"
|
2018-02-10 15:59:19 +00:00
|
|
|
"strconv"
|
2016-08-13 12:49:04 +00:00
|
|
|
"strings"
|
2018-01-26 21:28:07 +00:00
|
|
|
"time"
|
2016-08-13 12:49:04 +00:00
|
|
|
)
|
|
|
|
|
2016-12-18 15:01:45 +00:00
|
|
|
type linkState byte
|
2016-10-24 19:18:31 +00:00
|
|
|
|
|
|
|
const (
|
2016-12-17 21:47:37 +00:00
|
|
|
notLink linkState = iota
|
|
|
|
working
|
|
|
|
broken
|
2016-10-24 19:18:31 +00:00
|
|
|
)
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type file struct {
|
2016-10-24 19:18:31 +00:00
|
|
|
os.FileInfo
|
2017-11-19 18:55:13 +00:00
|
|
|
linkState linkState
|
|
|
|
path string
|
2018-05-20 17:30:41 +00:00
|
|
|
dirCount int
|
2016-11-10 21:18:56 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func readdir(path string) ([]*file, error) {
|
2016-10-24 19:18:31 +00:00
|
|
|
f, err := os.Open(path)
|
2016-08-13 12:49:04 +00:00
|
|
|
if err != nil {
|
2016-10-24 19:18:31 +00:00
|
|
|
return nil, err
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
2016-10-24 19:18:31 +00:00
|
|
|
names, err := f.Readdirnames(-1)
|
2017-04-09 13:15:06 +00:00
|
|
|
f.Close()
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
files := make([]*file, 0, len(names))
|
|
|
|
for _, fname := range names {
|
|
|
|
fpath := filepath.Join(path, fname)
|
2016-10-24 19:18:31 +00:00
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
lstat, err := os.Lstat(fpath)
|
|
|
|
if os.IsNotExist(err) {
|
2016-10-24 19:18:31 +00:00
|
|
|
continue
|
|
|
|
}
|
2018-05-20 17:30:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return files, err
|
2016-10-24 19:18:31 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
var linkState linkState
|
2016-10-24 19:18:31 +00:00
|
|
|
|
|
|
|
if lstat.Mode()&os.ModeSymlink != 0 {
|
2018-05-20 17:30:41 +00:00
|
|
|
stat, err := os.Stat(fpath)
|
|
|
|
if err == nil {
|
2016-12-17 21:47:37 +00:00
|
|
|
linkState = working
|
2016-10-24 19:18:31 +00:00
|
|
|
lstat = stat
|
|
|
|
} else {
|
2016-12-17 21:47:37 +00:00
|
|
|
linkState = broken
|
2016-10-24 19:18:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
files = append(files, &file{
|
2016-10-24 19:18:31 +00:00
|
|
|
FileInfo: lstat,
|
2017-11-19 18:55:13 +00:00
|
|
|
linkState: linkState,
|
|
|
|
path: fpath,
|
2018-05-20 17:30:41 +00:00
|
|
|
dirCount: -1,
|
2016-10-24 19:18:31 +00:00
|
|
|
})
|
|
|
|
}
|
2017-11-19 18:55:13 +00:00
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
return files, err
|
2016-10-24 19:18:31 +00:00
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type dir struct {
|
2018-01-26 21:28:07 +00:00
|
|
|
loading bool // directory is loading from disk
|
|
|
|
loadTime time.Time // current loading or last load time
|
2018-05-20 17:30:41 +00:00
|
|
|
ind int // index of current entry in files
|
2018-01-26 21:28:07 +00:00
|
|
|
pos int // position of current entry in ui
|
|
|
|
path string // full path of directory
|
2018-05-20 17:30:41 +00:00
|
|
|
files []*file // displayed files in directory including or excluding hidden ones
|
|
|
|
allFiles []*file // all files in directory including hidden ones (same array as files)
|
2018-04-18 20:08:28 +00:00
|
|
|
sortType sortType // sort method and options from last sort
|
2016-10-24 19:18:31 +00:00
|
|
|
}
|
|
|
|
|
2017-11-19 18:55:13 +00:00
|
|
|
func newDir(path string) *dir {
|
2018-01-26 21:28:07 +00:00
|
|
|
time := time.Now()
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
files, err := readdir(path)
|
2017-11-18 19:06:30 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("reading directory: %s", err)
|
|
|
|
}
|
|
|
|
|
2017-11-19 18:55:13 +00:00
|
|
|
return &dir{
|
2018-01-26 21:28:07 +00:00
|
|
|
loadTime: time,
|
|
|
|
path: path,
|
2018-05-20 17:30:41 +00:00
|
|
|
files: files,
|
|
|
|
allFiles: files,
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-19 18:55:13 +00:00
|
|
|
func (dir *dir) sort() {
|
2018-04-18 20:08:28 +00:00
|
|
|
dir.sortType = gOpts.sortType
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
dir.files = dir.allFiles
|
2017-11-30 20:11:37 +00:00
|
|
|
|
2018-04-18 20:08:28 +00:00
|
|
|
switch gOpts.sortType.method {
|
|
|
|
case naturalSort:
|
2018-05-20 17:30:41 +00:00
|
|
|
sort.SliceStable(dir.files, func(i, j int) bool {
|
|
|
|
return naturalLess(strings.ToLower(dir.files[i].Name()), strings.ToLower(dir.files[j].Name()))
|
2017-11-18 19:06:30 +00:00
|
|
|
})
|
2018-04-18 20:08:28 +00:00
|
|
|
case nameSort:
|
2018-05-20 17:30:41 +00:00
|
|
|
sort.SliceStable(dir.files, func(i, j int) bool {
|
|
|
|
return strings.ToLower(dir.files[i].Name()) < strings.ToLower(dir.files[j].Name())
|
2017-11-18 19:06:30 +00:00
|
|
|
})
|
2018-04-18 20:08:28 +00:00
|
|
|
case sizeSort:
|
2018-05-20 17:30:41 +00:00
|
|
|
sort.SliceStable(dir.files, func(i, j int) bool {
|
|
|
|
return dir.files[i].Size() < dir.files[j].Size()
|
2017-11-18 19:06:30 +00:00
|
|
|
})
|
2018-04-18 20:08:28 +00:00
|
|
|
case timeSort:
|
2018-05-20 17:30:41 +00:00
|
|
|
sort.SliceStable(dir.files, func(i, j int) bool {
|
|
|
|
return dir.files[i].ModTime().Before(dir.files[j].ModTime())
|
2017-11-18 19:06:30 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-04-18 20:08:28 +00:00
|
|
|
if gOpts.sortType.option&reverseSort != 0 {
|
2018-05-20 17:30:41 +00:00
|
|
|
for i, j := 0, len(dir.files)-1; i < j; i, j = i+1, j-1 {
|
|
|
|
dir.files[i], dir.files[j] = dir.files[j], dir.files[i]
|
2017-11-18 19:06:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-18 20:08:28 +00:00
|
|
|
if gOpts.sortType.option&dirfirstSort != 0 {
|
2018-05-20 17:30:41 +00:00
|
|
|
sort.SliceStable(dir.files, func(i, j int) bool {
|
|
|
|
if dir.files[i].IsDir() == dir.files[j].IsDir() {
|
2017-11-18 19:06:30 +00:00
|
|
|
return i < j
|
|
|
|
}
|
2018-05-20 17:30:41 +00:00
|
|
|
return dir.files[i].IsDir()
|
2017-11-18 19:06:30 +00:00
|
|
|
})
|
|
|
|
}
|
2017-11-30 20:11:37 +00:00
|
|
|
|
|
|
|
// when hidden option is disabled, we move hidden files to the
|
|
|
|
// beginning of our file list and then set the beginning of displayed
|
|
|
|
// files to the first non-hidden file in the list
|
2018-04-18 20:08:28 +00:00
|
|
|
if gOpts.sortType.option&hiddenSort == 0 {
|
2018-05-20 17:30:41 +00:00
|
|
|
sort.SliceStable(dir.files, func(i, j int) bool {
|
|
|
|
if dir.files[i].Name()[0] == '.' && dir.files[j].Name()[0] == '.' {
|
2017-11-30 20:11:37 +00:00
|
|
|
return i < j
|
|
|
|
}
|
2018-05-20 17:30:41 +00:00
|
|
|
return dir.files[i].Name()[0] == '.'
|
2017-11-30 20:11:37 +00:00
|
|
|
})
|
2018-05-20 17:30:41 +00:00
|
|
|
for i, f := range dir.files {
|
2017-11-30 20:11:37 +00:00
|
|
|
if f.Name()[0] != '.' {
|
2018-05-20 17:30:41 +00:00
|
|
|
dir.files = dir.files[i:]
|
2017-11-30 20:11:37 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2018-05-20 17:30:41 +00:00
|
|
|
dir.files = dir.files[len(dir.files):]
|
2017-11-30 20:11:37 +00:00
|
|
|
}
|
2017-11-18 19:06:30 +00:00
|
|
|
}
|
|
|
|
|
2017-11-19 18:55:13 +00:00
|
|
|
func (dir *dir) name() string {
|
2018-05-20 17:30:41 +00:00
|
|
|
if len(dir.files) == 0 {
|
2017-11-19 18:55:13 +00:00
|
|
|
return ""
|
|
|
|
}
|
2018-05-20 17:30:41 +00:00
|
|
|
return dir.files[dir.ind].Name()
|
2017-11-19 18:55:13 +00:00
|
|
|
}
|
|
|
|
|
2018-08-22 17:05:22 +00:00
|
|
|
func (dir *dir) sel(name string, height int) {
|
2018-05-20 17:30:41 +00:00
|
|
|
if len(dir.files) == 0 {
|
2016-08-13 12:49:04 +00:00
|
|
|
dir.ind, dir.pos = 0, 0
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
dir.ind = min(dir.ind, len(dir.files)-1)
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
if dir.files[dir.ind].Name() != name {
|
|
|
|
for i, f := range dir.files {
|
2016-08-13 12:49:04 +00:00
|
|
|
if f.Name() == name {
|
2017-11-19 18:55:13 +00:00
|
|
|
dir.ind = i
|
2016-08-13 12:49:04 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-11-30 20:11:37 +00:00
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
edge := min(min(height/2, gOpts.scrolloff), len(dir.files)-dir.ind-1)
|
2017-11-30 20:11:37 +00:00
|
|
|
dir.pos = min(dir.ind, height-edge-1)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type nav struct {
|
2018-07-09 18:22:10 +00:00
|
|
|
dirs []*dir
|
|
|
|
dirChan chan *dir
|
|
|
|
regChan chan *reg
|
|
|
|
dirCache map[string]*dir
|
|
|
|
regCache map[string]*reg
|
|
|
|
saves map[string]bool
|
2018-07-09 18:35:04 +00:00
|
|
|
marks map[string]string
|
2018-07-09 18:22:10 +00:00
|
|
|
selections map[string]int
|
|
|
|
selectionInd int
|
|
|
|
height int
|
2018-08-22 17:05:22 +00:00
|
|
|
find string
|
2018-08-22 22:55:50 +00:00
|
|
|
findBack bool
|
2018-07-09 18:22:10 +00:00
|
|
|
search string
|
2018-08-22 22:55:50 +00:00
|
|
|
searchBack bool
|
2018-11-24 16:02:04 +00:00
|
|
|
searchInd int
|
|
|
|
searchPos int
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
func (nav *nav) loadDir(path string) *dir {
|
|
|
|
d, ok := nav.dirCache[path]
|
|
|
|
if !ok {
|
|
|
|
go func() {
|
|
|
|
d := newDir(path)
|
|
|
|
d.sort()
|
|
|
|
d.ind, d.pos = 0, 0
|
|
|
|
nav.dirChan <- d
|
|
|
|
}()
|
|
|
|
d := &dir{loading: true, path: path, sortType: gOpts.sortType}
|
|
|
|
nav.dirCache[path] = d
|
|
|
|
return d
|
2017-11-18 22:25:41 +00:00
|
|
|
}
|
|
|
|
|
2018-06-07 19:19:55 +00:00
|
|
|
s, err := os.Stat(d.path)
|
|
|
|
if err != nil {
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case s.ModTime().After(d.loadTime):
|
|
|
|
go func() {
|
|
|
|
d.loadTime = time.Now()
|
|
|
|
nd := newDir(path)
|
|
|
|
nd.sort()
|
2018-08-22 17:05:22 +00:00
|
|
|
nd.sel(d.name(), nav.height)
|
2018-06-07 19:19:55 +00:00
|
|
|
nav.dirChan <- nd
|
|
|
|
}()
|
|
|
|
case d.sortType != gOpts.sortType:
|
2018-05-20 17:30:41 +00:00
|
|
|
go func() {
|
|
|
|
d.loading = true
|
|
|
|
name := d.name()
|
|
|
|
d.sort()
|
2018-08-22 17:05:22 +00:00
|
|
|
d.sel(name, nav.height)
|
2018-05-20 17:30:41 +00:00
|
|
|
d.loading = false
|
|
|
|
nav.dirChan <- d
|
|
|
|
}()
|
2017-11-18 22:25:41 +00:00
|
|
|
}
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
return d
|
2018-01-11 16:25:48 +00:00
|
|
|
}
|
|
|
|
|
2017-11-18 22:25:41 +00:00
|
|
|
func (nav *nav) getDirs(wd string) {
|
2016-12-17 21:47:37 +00:00
|
|
|
var dirs []*dir
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-09-06 20:05:18 +00:00
|
|
|
for curr, base := wd, ""; !isRoot(base); curr, base = filepath.Dir(curr), filepath.Base(curr) {
|
2018-02-10 15:59:19 +00:00
|
|
|
dir := nav.loadDir(curr)
|
2018-08-22 17:05:22 +00:00
|
|
|
dir.sel(base, nav.height)
|
2016-08-13 12:49:04 +00:00
|
|
|
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]
|
|
|
|
}
|
|
|
|
|
2017-11-18 22:25:41 +00:00
|
|
|
nav.dirs = dirs
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
func newNav(height int) *nav {
|
|
|
|
wd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("getting current directory: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
nav := &nav{
|
2018-07-09 18:22:10 +00:00
|
|
|
dirChan: make(chan *dir),
|
|
|
|
regChan: make(chan *reg),
|
|
|
|
dirCache: make(map[string]*dir),
|
|
|
|
regCache: make(map[string]*reg),
|
|
|
|
saves: make(map[string]bool),
|
2018-07-09 18:35:04 +00:00
|
|
|
marks: make(map[string]string),
|
2018-07-09 18:22:10 +00:00
|
|
|
selections: make(map[string]int),
|
|
|
|
selectionInd: 0,
|
|
|
|
height: height,
|
2018-05-20 17:30:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
nav.getDirs(wd)
|
|
|
|
|
|
|
|
return nav
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:18:39 +00:00
|
|
|
func (nav *nav) renew() {
|
2016-08-13 12:49:04 +00:00
|
|
|
for _, d := range nav.dirs {
|
2018-01-26 21:28:07 +00:00
|
|
|
go func(d *dir) {
|
|
|
|
s, err := os.Stat(d.path)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("getting directory info: %s", err)
|
2018-07-30 15:41:14 +00:00
|
|
|
return
|
2018-01-26 21:28:07 +00:00
|
|
|
}
|
|
|
|
if d.loadTime.After(s.ModTime()) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
d.loadTime = time.Now()
|
|
|
|
nd := newDir(d.path)
|
|
|
|
nd.sort()
|
|
|
|
nav.dirChan <- nd
|
|
|
|
}(d)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2018-07-09 18:22:10 +00:00
|
|
|
for m := range nav.selections {
|
2016-08-13 12:49:04 +00:00
|
|
|
if _, err := os.Stat(m); os.IsNotExist(err) {
|
2018-07-09 18:22:10 +00:00
|
|
|
delete(nav.selections, m)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
}
|
2018-07-09 18:22:10 +00:00
|
|
|
if len(nav.selections) == 0 {
|
|
|
|
nav.selectionInd = 0
|
2017-01-05 21:23:22 +00:00
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2018-07-30 17:56:57 +00:00
|
|
|
func (nav *nav) reload() error {
|
2018-01-29 15:08:51 +00:00
|
|
|
nav.dirCache = make(map[string]*dir)
|
2018-02-10 15:59:19 +00:00
|
|
|
nav.regCache = make(map[string]*reg)
|
2018-01-29 15:08:51 +00:00
|
|
|
|
|
|
|
wd, err := os.Getwd()
|
|
|
|
if err != nil {
|
2018-07-30 17:56:57 +00:00
|
|
|
return fmt.Errorf("getting current directory: %s", err)
|
2018-01-29 15:08:51 +00:00
|
|
|
}
|
|
|
|
|
2018-03-22 14:47:36 +00:00
|
|
|
curr, err := nav.currFile()
|
2018-01-29 15:08:51 +00:00
|
|
|
nav.getDirs(wd)
|
2018-03-22 14:47:36 +00:00
|
|
|
if err == nil {
|
|
|
|
last := nav.dirs[len(nav.dirs)-1]
|
2018-05-20 17:30:41 +00:00
|
|
|
last.files = append(last.files, curr)
|
2018-03-22 14:47:36 +00:00
|
|
|
}
|
2018-07-30 17:56:57 +00:00
|
|
|
|
|
|
|
return nil
|
2018-01-29 15:08:51 +00:00
|
|
|
}
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
func (nav *nav) position() {
|
|
|
|
path := nav.currDir().path
|
|
|
|
for i := len(nav.dirs) - 2; i >= 0; i-- {
|
2018-08-22 17:05:22 +00:00
|
|
|
nav.dirs[i].sel(filepath.Base(path), nav.height)
|
2018-05-20 17:30:41 +00:00
|
|
|
path = filepath.Dir(path)
|
2018-04-18 20:08:28 +00:00
|
|
|
}
|
2017-11-18 22:25:41 +00:00
|
|
|
}
|
|
|
|
|
2018-02-10 15:59:19 +00:00
|
|
|
func (nav *nav) preview() {
|
|
|
|
curr, err := nav.currFile()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var reader io.Reader
|
|
|
|
|
|
|
|
if len(gOpts.previewer) != 0 {
|
|
|
|
cmd := exec.Command(gOpts.previewer, curr.path, strconv.Itoa(nav.height))
|
|
|
|
|
|
|
|
out, err := cmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("previewing file: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
log.Printf("previewing file: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer cmd.Wait()
|
|
|
|
defer out.Close()
|
|
|
|
reader = out
|
|
|
|
} else {
|
|
|
|
f, err := os.Open(curr.path)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("opening file: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer f.Close()
|
|
|
|
reader = f
|
|
|
|
}
|
|
|
|
|
2018-06-07 19:49:53 +00:00
|
|
|
reg := ®{loadTime: time.Now(), path: curr.path}
|
2018-02-10 15:59:19 +00:00
|
|
|
|
|
|
|
buf := bufio.NewScanner(reader)
|
|
|
|
|
|
|
|
for i := 0; i < nav.height && buf.Scan(); i++ {
|
|
|
|
for _, r := range buf.Text() {
|
|
|
|
if r == 0 {
|
|
|
|
reg.lines = []string{"\033[1mbinary\033[0m"}
|
|
|
|
nav.regChan <- reg
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
reg.lines = append(reg.lines, buf.Text())
|
|
|
|
}
|
|
|
|
|
|
|
|
if buf.Err() != nil {
|
|
|
|
log.Printf("loading file: %s", buf.Err())
|
|
|
|
}
|
|
|
|
|
|
|
|
nav.regChan <- reg
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *nav) loadReg(ui *ui, path string) *reg {
|
|
|
|
r, ok := nav.regCache[path]
|
|
|
|
if !ok {
|
|
|
|
go nav.preview()
|
2018-06-07 19:49:53 +00:00
|
|
|
r := ®{loading: true, path: path}
|
2018-02-10 15:59:19 +00:00
|
|
|
nav.regCache[path] = r
|
|
|
|
return r
|
|
|
|
}
|
2018-06-07 19:49:53 +00:00
|
|
|
|
|
|
|
s, err := os.Stat(r.path)
|
|
|
|
if err != nil {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.ModTime().After(r.loadTime) {
|
|
|
|
r.loadTime = time.Now()
|
|
|
|
go nav.preview()
|
|
|
|
}
|
|
|
|
|
2018-02-10 15:59:19 +00:00
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2017-11-18 19:06:30 +00:00
|
|
|
func (nav *nav) sort() {
|
|
|
|
for _, d := range nav.dirs {
|
2017-11-19 18:55:13 +00:00
|
|
|
name := d.name()
|
|
|
|
d.sort()
|
2018-08-22 17:05:22 +00:00
|
|
|
d.sel(name, nav.height)
|
2017-11-18 19:06:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +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
|
2018-04-15 15:18:39 +00:00
|
|
|
edge := min(min(nav.height/2, gOpts.scrolloff), dir.ind)
|
2016-08-14 15:39:02 +00:00
|
|
|
dir.pos = max(dir.pos, edge)
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (nav *nav) down(dist int) {
|
2016-08-13 12:49:04 +00:00
|
|
|
dir := nav.currDir()
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
maxind := len(dir.files) - 1
|
2016-08-13 12:49:04 +00:00
|
|
|
|
|
|
|
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
|
2018-04-15 15:18:39 +00:00
|
|
|
edge := min(min(nav.height/2, 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)
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (nav *nav) updir() error {
|
2016-08-13 12:49:04 +00:00
|
|
|
if len(nav.dirs) <= 1 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
dir := nav.currDir()
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (nav *nav) open() error {
|
2016-10-24 19:18:31 +00:00
|
|
|
curr, err := nav.currFile()
|
|
|
|
if err != nil {
|
2016-12-18 15:01:45 +00:00
|
|
|
return fmt.Errorf("open: %s", err)
|
2016-10-24 19:18:31 +00:00
|
|
|
}
|
2016-12-18 15:01:45 +00:00
|
|
|
|
2017-11-19 18:55:13 +00:00
|
|
|
path := curr.path
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2018-02-10 15:59:19 +00:00
|
|
|
dir := nav.loadDir(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
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (nav *nav) top() {
|
2016-08-13 12:49:04 +00:00
|
|
|
dir := nav.currDir()
|
|
|
|
|
|
|
|
dir.ind = 0
|
|
|
|
dir.pos = 0
|
|
|
|
}
|
|
|
|
|
2018-05-15 21:20:05 +00:00
|
|
|
func (nav *nav) bottom() {
|
2017-11-19 18:55:13 +00:00
|
|
|
dir := nav.currDir()
|
2016-12-23 15:58:24 +00:00
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
dir.ind = len(dir.files) - 1
|
2017-11-19 18:55:13 +00:00
|
|
|
dir.pos = min(dir.ind, nav.height-1)
|
2016-12-23 15:58:24 +00:00
|
|
|
}
|
|
|
|
|
2018-07-09 18:22:10 +00:00
|
|
|
func (nav *nav) toggleSelection(path string) {
|
|
|
|
if _, ok := nav.selections[path]; ok {
|
|
|
|
delete(nav.selections, path)
|
|
|
|
if len(nav.selections) == 0 {
|
|
|
|
nav.selectionInd = 0
|
2017-01-05 21:23:22 +00:00
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
} else {
|
2018-07-09 18:22:10 +00:00
|
|
|
nav.selections[path] = nav.selectionInd
|
|
|
|
nav.selectionInd++
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
2016-10-09 16:07:57 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (nav *nav) toggle() {
|
2016-10-24 19:18:31 +00:00
|
|
|
curr, err := nav.currFile()
|
|
|
|
if err != nil {
|
2016-10-09 16:07:57 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-07-09 18:22:10 +00:00
|
|
|
nav.toggleSelection(curr.path)
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-09-08 21:04:44 +00:00
|
|
|
nav.down(1)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (nav *nav) invert() {
|
2016-10-09 16:07:57 +00:00
|
|
|
last := nav.currDir()
|
2018-05-20 17:30:41 +00:00
|
|
|
for _, f := range last.files {
|
2016-10-09 16:07:57 +00:00
|
|
|
path := filepath.Join(last.path, f.Name())
|
2018-07-09 18:22:10 +00:00
|
|
|
nav.toggleSelection(path)
|
2016-10-09 16:07:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-09 18:22:10 +00:00
|
|
|
func (nav *nav) unselect() {
|
|
|
|
nav.selections = make(map[string]int)
|
|
|
|
nav.selectionInd = 0
|
2017-11-25 13:15:04 +00:00
|
|
|
}
|
|
|
|
|
2018-12-29 17:14:20 +00:00
|
|
|
// effectiveSelection is a pure function that returns the selected files's paths.
|
|
|
|
// In case the user has not selected a file it returns the file on the user's cursor.
|
|
|
|
// If the function can't return a selection it returns an error.
|
|
|
|
func (nav *nav) effectiveSelection() (list []string, err error) {
|
|
|
|
if len(nav.selections) == 0 {
|
|
|
|
curr, err := nav.currFile()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("no file selected")
|
|
|
|
}
|
|
|
|
|
|
|
|
return []string{curr.path}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nav.currSelections(), nil
|
|
|
|
}
|
|
|
|
|
2018-06-27 18:15:34 +00:00
|
|
|
func (nav *nav) save(cp bool) error {
|
2018-07-09 18:22:10 +00:00
|
|
|
if len(nav.selections) == 0 {
|
2016-10-24 19:18:31 +00:00
|
|
|
curr, err := nav.currFile()
|
|
|
|
if err != nil {
|
2016-10-05 15:26:55 +00:00
|
|
|
return errors.New("no file selected")
|
|
|
|
}
|
|
|
|
|
2018-06-27 18:15:34 +00:00
|
|
|
if err := saveFiles([]string{curr.path}, cp); err != nil {
|
2016-08-13 12:49:04 +00:00
|
|
|
return err
|
|
|
|
}
|
2016-11-07 20:32:19 +00:00
|
|
|
|
|
|
|
nav.saves = make(map[string]bool)
|
2018-06-27 18:15:34 +00:00
|
|
|
nav.saves[curr.path] = cp
|
2016-08-13 12:49:04 +00:00
|
|
|
} else {
|
2018-07-09 18:22:10 +00:00
|
|
|
selections := nav.currSelections()
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2018-07-09 18:22:10 +00:00
|
|
|
if err := saveFiles(selections, cp); err != nil {
|
2016-08-13 12:49:04 +00:00
|
|
|
return err
|
|
|
|
}
|
2016-11-07 20:32:19 +00:00
|
|
|
|
|
|
|
nav.saves = make(map[string]bool)
|
2018-07-09 18:22:10 +00:00
|
|
|
for f := range nav.selections {
|
2018-06-27 18:15:34 +00:00
|
|
|
nav.saves[f] = cp
|
2016-11-07 20:32:19 +00:00
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-06-27 18:15:34 +00:00
|
|
|
func (nav *nav) paste() error {
|
|
|
|
list, cp, err := loadFiles()
|
2016-08-13 12:49:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(list) == 0 {
|
2018-06-27 18:15:34 +00:00
|
|
|
return errors.New("no file in copy/cut buffer")
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dir := nav.currDir()
|
|
|
|
|
2018-06-27 18:15:34 +00:00
|
|
|
cmd := pasteCommand(list, dir, cp)
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2017-08-05 16:23:55 +00:00
|
|
|
if err := cmd.Run(); err != nil {
|
2018-06-27 18:15:34 +00:00
|
|
|
return fmt.Errorf("pasting files: %s", err)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2018-03-22 14:54:24 +00:00
|
|
|
if err := saveFiles(nil, false); err != nil {
|
2018-06-27 18:15:34 +00:00
|
|
|
return fmt.Errorf("clearing copy/cut buffer: %s", err)
|
2018-03-22 14:54:24 +00:00
|
|
|
}
|
2017-10-26 18:16:24 +00:00
|
|
|
|
2016-08-13 12:49:04 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-12-29 17:14:20 +00:00
|
|
|
// deleteFiles deletes the user's selected files
|
|
|
|
// it returns an error if no files are selected or if the OS fails to delete a file
|
|
|
|
func (nav *nav) deleteFiles() error {
|
|
|
|
list, err := nav.effectiveSelection()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, path := range list {
|
|
|
|
if err := os.RemoveAll(path); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (nav *nav) sync() error {
|
2018-06-27 18:15:34 +00:00
|
|
|
list, cp, err := loadFiles()
|
2016-11-10 20:43:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
nav.saves = make(map[string]bool)
|
|
|
|
for _, f := range list {
|
2018-06-27 18:15:34 +00:00
|
|
|
nav.saves[f] = cp
|
2016-11-10 20:43:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-11-19 18:55:13 +00:00
|
|
|
func (nav *nav) cd(wd string) error {
|
|
|
|
wd = strings.Replace(wd, "~", gUser.HomeDir, -1)
|
|
|
|
wd = filepath.Clean(wd)
|
|
|
|
|
|
|
|
if !filepath.IsAbs(wd) {
|
|
|
|
wd = filepath.Join(nav.currDir().path, wd)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.Chdir(wd); err != nil {
|
|
|
|
return fmt.Errorf("cd: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
nav.getDirs(wd)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-22 17:05:22 +00:00
|
|
|
func (nav *nav) sel(path string) error {
|
2018-03-27 17:47:17 +00:00
|
|
|
lstat, err := os.Stat(path)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("select: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
dir := filepath.Dir(path)
|
|
|
|
|
|
|
|
if err := nav.cd(dir); err != nil {
|
|
|
|
return fmt.Errorf("select: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
base := filepath.Base(path)
|
|
|
|
|
|
|
|
last := nav.dirs[len(nav.dirs)-1]
|
|
|
|
if last.loading {
|
2018-05-20 17:30:41 +00:00
|
|
|
last.files = append(last.files, &file{FileInfo: lstat})
|
2018-03-27 17:47:17 +00:00
|
|
|
} else {
|
2018-08-22 17:05:22 +00:00
|
|
|
last.sel(base, nav.height)
|
2018-03-27 17:47:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-22 17:05:22 +00:00
|
|
|
func findMatch(name, pattern string) bool {
|
|
|
|
if gOpts.ignorecase {
|
|
|
|
lpattern := strings.ToLower(pattern)
|
|
|
|
if !gOpts.smartcase || lpattern == pattern {
|
|
|
|
pattern = lpattern
|
|
|
|
name = strings.ToLower(name)
|
|
|
|
}
|
|
|
|
}
|
2018-12-03 12:41:53 +00:00
|
|
|
if gOpts.ignoredia {
|
|
|
|
lpattern := removeDiacritics(pattern)
|
|
|
|
if !gOpts.smartdia || lpattern == pattern {
|
|
|
|
pattern = lpattern
|
|
|
|
name = removeDiacritics(name)
|
|
|
|
}
|
|
|
|
}
|
2018-08-22 22:37:07 +00:00
|
|
|
if gOpts.anchorfind {
|
|
|
|
return strings.HasPrefix(name, pattern)
|
|
|
|
}
|
|
|
|
return strings.Contains(name, pattern)
|
2018-08-22 17:05:22 +00:00
|
|
|
}
|
|
|
|
|
2018-08-22 22:29:59 +00:00
|
|
|
func (nav *nav) findSingle() int {
|
|
|
|
count := 0
|
|
|
|
index := 0
|
|
|
|
last := nav.currDir()
|
|
|
|
for i := 0; i < len(last.files); i++ {
|
|
|
|
if findMatch(last.files[i].Name(), nav.find) {
|
|
|
|
count++
|
|
|
|
if count > 1 {
|
|
|
|
return count
|
|
|
|
}
|
|
|
|
index = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if count == 1 {
|
|
|
|
if index > last.ind {
|
|
|
|
nav.down(index - last.ind)
|
|
|
|
} else {
|
|
|
|
nav.up(last.ind - index)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count
|
|
|
|
}
|
|
|
|
|
2018-08-22 17:05:22 +00:00
|
|
|
func (nav *nav) findNext() bool {
|
|
|
|
last := nav.currDir()
|
|
|
|
for i := last.ind + 1; i < len(last.files); i++ {
|
|
|
|
if findMatch(last.files[i].Name(), nav.find) {
|
|
|
|
nav.down(i - last.ind)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if gOpts.wrapscan {
|
|
|
|
for i := 0; i < last.ind; i++ {
|
|
|
|
if findMatch(last.files[i].Name(), nav.find) {
|
2018-08-22 22:29:59 +00:00
|
|
|
nav.up(last.ind - i)
|
2018-08-22 17:05:22 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *nav) findPrev() bool {
|
|
|
|
last := nav.currDir()
|
|
|
|
for i := last.ind - 1; i >= 0; i-- {
|
|
|
|
if findMatch(last.files[i].Name(), nav.find) {
|
2018-08-22 22:29:59 +00:00
|
|
|
nav.up(last.ind - i)
|
2018-08-22 17:05:22 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if gOpts.wrapscan {
|
|
|
|
for i := len(last.files) - 1; i > last.ind; i-- {
|
|
|
|
if findMatch(last.files[i].Name(), nav.find) {
|
|
|
|
nav.down(i - last.ind)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func searchMatch(name, pattern string) (matched bool, err error) {
|
2017-11-19 18:55:13 +00:00
|
|
|
if gOpts.ignorecase {
|
|
|
|
lpattern := strings.ToLower(pattern)
|
|
|
|
if !gOpts.smartcase || lpattern == pattern {
|
|
|
|
pattern = lpattern
|
|
|
|
name = strings.ToLower(name)
|
|
|
|
}
|
|
|
|
}
|
2018-12-03 12:41:53 +00:00
|
|
|
if gOpts.ignoredia {
|
|
|
|
lpattern := removeDiacritics(pattern)
|
|
|
|
if !gOpts.smartdia || lpattern == pattern {
|
|
|
|
pattern = lpattern
|
|
|
|
name = removeDiacritics(name)
|
|
|
|
}
|
|
|
|
}
|
2017-11-19 18:55:13 +00:00
|
|
|
if gOpts.globsearch {
|
|
|
|
return filepath.Match(pattern, name)
|
|
|
|
}
|
|
|
|
return strings.Contains(name, pattern), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *nav) searchNext() error {
|
|
|
|
last := nav.currDir()
|
2018-05-20 17:30:41 +00:00
|
|
|
for i := last.ind + 1; i < len(last.files); i++ {
|
2018-08-22 17:05:22 +00:00
|
|
|
matched, err := searchMatch(last.files[i].Name(), nav.search)
|
2017-11-19 18:55:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if matched {
|
|
|
|
nav.down(i - last.ind)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if gOpts.wrapscan {
|
|
|
|
for i := 0; i < last.ind; i++ {
|
2018-08-22 17:05:22 +00:00
|
|
|
matched, err := searchMatch(last.files[i].Name(), nav.search)
|
2017-11-19 18:55:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if matched {
|
|
|
|
nav.up(last.ind - i)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *nav) searchPrev() error {
|
|
|
|
last := nav.currDir()
|
|
|
|
for i := last.ind - 1; i >= 0; i-- {
|
2018-08-22 17:05:22 +00:00
|
|
|
matched, err := searchMatch(last.files[i].Name(), nav.search)
|
2017-11-19 18:55:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if matched {
|
|
|
|
nav.up(last.ind - i)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if gOpts.wrapscan {
|
2018-05-20 17:30:41 +00:00
|
|
|
for i := len(last.files) - 1; i > last.ind; i-- {
|
2018-08-22 17:05:22 +00:00
|
|
|
matched, err := searchMatch(last.files[i].Name(), nav.search)
|
2017-11-19 18:55:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if matched {
|
|
|
|
nav.down(i - last.ind)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (nav *nav) currDir() *dir {
|
2016-08-13 12:49:04 +00:00
|
|
|
return nav.dirs[len(nav.dirs)-1]
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (nav *nav) currFile() (*file, error) {
|
2016-08-13 12:49:04 +00:00
|
|
|
last := nav.dirs[len(nav.dirs)-1]
|
|
|
|
|
2018-05-20 17:30:41 +00:00
|
|
|
if len(last.files) == 0 {
|
2016-10-24 19:18:31 +00:00
|
|
|
return nil, fmt.Errorf("empty directory")
|
|
|
|
}
|
2018-05-20 17:30:41 +00:00
|
|
|
return last.files[last.ind], nil
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2018-07-11 17:09:26 +00:00
|
|
|
func (nav *nav) readMarks() error {
|
|
|
|
f, err := os.Open(gMarksPath)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("opening marks file: %s", err)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
|
|
for scanner.Scan() {
|
|
|
|
toks := strings.SplitN(scanner.Text(), ":", 2)
|
|
|
|
if _, ok := nav.marks[toks[0]]; !ok {
|
|
|
|
nav.marks[toks[0]] = toks[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
|
|
return fmt.Errorf("reading marks file: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nav *nav) writeMarks() error {
|
|
|
|
if len(nav.marks) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := nav.readMarks(); err != nil {
|
|
|
|
return fmt.Errorf("reading marks file: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.MkdirAll(filepath.Dir(gMarksPath), os.ModePerm); err != nil {
|
|
|
|
return fmt.Errorf("creating data directory: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
f, err := os.Create(gMarksPath)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("creating marks file: %s", err)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
var keys []string
|
|
|
|
for k := range nav.marks {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
for _, k := range keys {
|
|
|
|
_, err = f.WriteString(fmt.Sprintf("%s:%s\n", k, nav.marks[k]))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("writing marks file: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-07-09 18:22:10 +00:00
|
|
|
type indexedSelections struct {
|
2017-01-05 21:23:22 +00:00
|
|
|
paths []string
|
|
|
|
indices []int
|
|
|
|
}
|
|
|
|
|
2018-07-09 18:22:10 +00:00
|
|
|
func (m indexedSelections) Len() int { return len(m.paths) }
|
2017-02-11 13:34:18 +00:00
|
|
|
|
2018-07-09 18:22:10 +00:00
|
|
|
func (m indexedSelections) Swap(i, j int) {
|
2017-01-05 21:23:22 +00:00
|
|
|
m.paths[i], m.paths[j] = m.paths[j], m.paths[i]
|
|
|
|
m.indices[i], m.indices[j] = m.indices[j], m.indices[i]
|
|
|
|
}
|
2017-02-11 13:34:18 +00:00
|
|
|
|
2018-07-09 18:22:10 +00:00
|
|
|
func (m indexedSelections) Less(i, j int) bool { return m.indices[i] < m.indices[j] }
|
2017-01-05 21:23:22 +00:00
|
|
|
|
2018-07-09 18:22:10 +00:00
|
|
|
func (nav *nav) currSelections() []string {
|
|
|
|
paths := make([]string, 0, len(nav.selections))
|
|
|
|
indices := make([]int, 0, len(nav.selections))
|
|
|
|
for path, index := range nav.selections {
|
2017-01-05 21:23:22 +00:00
|
|
|
paths = append(paths, path)
|
|
|
|
indices = append(indices, index)
|
|
|
|
}
|
2018-07-09 18:22:10 +00:00
|
|
|
sort.Sort(indexedSelections{paths: paths, indices: indices})
|
2017-01-05 21:23:22 +00:00
|
|
|
return paths
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|