Refactor Nav API around symlinks handling (mostly) (#33)
* gofmt ui.go * Refactor Nav API around symlinks handling (mostly) 1. Get both `Lstat` and `Stat` while reading the files. Stat info is used by default, but we still can tell it's a link if needed. 2. Remove all other `Stat` calls from different places. 3. Handle hidden files while reading. 4. `currFile` now returns error if there are no files. 5. `currEmpty` and `currPath` not longer necessary, removed. 6. `open` now returns a named error ErrNotDir, where expected. 7. Side effect: Links that point at directories are now sorted among "actual" directories.
This commit is contained in:
parent
880615ddd0
commit
c9b4389c65
6
app.go
6
app.go
@ -76,11 +76,9 @@ func (app *App) handleInp() {
|
||||
}
|
||||
|
||||
func (app *App) exportVars() {
|
||||
dir := app.nav.currDir()
|
||||
|
||||
var envFile string
|
||||
if len(dir.fi) != 0 {
|
||||
envFile = app.nav.currPath()
|
||||
if f, err := app.nav.currFile(); err == nil {
|
||||
envFile = f.Path
|
||||
}
|
||||
|
||||
marks := app.nav.currMarks()
|
||||
|
28
eval.go
28
eval.go
@ -178,31 +178,16 @@ func (e *CallExpr) eval(app *App, args []string) {
|
||||
}
|
||||
app.ui.loadFile(app.nav)
|
||||
case "open":
|
||||
dir := app.nav.currDir()
|
||||
|
||||
if len(dir.fi) == 0 {
|
||||
err := app.nav.open()
|
||||
if err == nil {
|
||||
app.ui.loadFile(app.nav)
|
||||
return
|
||||
}
|
||||
|
||||
path := app.nav.currPath()
|
||||
|
||||
f, err := os.Stat(path)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("open: %s", err)
|
||||
app.ui.message = msg
|
||||
log.Print(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
if err := app.nav.open(); err != nil {
|
||||
if err != ErrNotDir {
|
||||
app.ui.message = err.Error()
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
app.ui.loadFile(app.nav)
|
||||
return
|
||||
}
|
||||
|
||||
if gSelectionPath != "" {
|
||||
out, err := os.Create(gSelectionPath)
|
||||
@ -211,9 +196,14 @@ func (e *CallExpr) eval(app *App, args []string) {
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
var path string
|
||||
if len(app.nav.marks) != 0 {
|
||||
marks := app.nav.currMarks()
|
||||
path = strings.Join(marks, "\n")
|
||||
} else if curr, err := app.nav.currFile(); err == nil {
|
||||
path = curr.Path
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = out.WriteString(path)
|
||||
|
144
nav.go
144
nav.go
@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
@ -12,14 +11,21 @@ import (
|
||||
"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 LinkState int8
|
||||
|
||||
const (
|
||||
NotLink LinkState = iota
|
||||
Working
|
||||
Broken
|
||||
)
|
||||
|
||||
type File struct {
|
||||
os.FileInfo
|
||||
LinkState LinkState
|
||||
Path string
|
||||
}
|
||||
|
||||
type ByName []os.FileInfo
|
||||
type ByName []*File
|
||||
|
||||
func (a ByName) Len() int { return len(a) }
|
||||
func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
@ -28,19 +34,19 @@ func (a ByName) Less(i, j int) bool {
|
||||
return strings.ToLower(a[i].Name()) < strings.ToLower(a[j].Name())
|
||||
}
|
||||
|
||||
type BySize []os.FileInfo
|
||||
type BySize []*File
|
||||
|
||||
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
|
||||
type ByTime []*File
|
||||
|
||||
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
|
||||
type ByDir []*File
|
||||
|
||||
func (a ByDir) Len() int { return len(a) }
|
||||
func (a ByDir) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
@ -52,7 +58,7 @@ func (a ByDir) Less(i, j int) bool {
|
||||
return a[i].IsDir()
|
||||
}
|
||||
|
||||
type ByNum []os.FileInfo
|
||||
type ByNum []*File
|
||||
|
||||
func (a ByNum) Len() int { return len(a) }
|
||||
func (a ByNum) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
@ -85,15 +91,10 @@ func (a ByNum) Less(i, j int) bool {
|
||||
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
|
||||
func getFilesSorted(path string) []*File {
|
||||
fi, err := readdir(path)
|
||||
if err != nil {
|
||||
log.Printf("reading directory: %s", err)
|
||||
}
|
||||
|
||||
switch gOpts.sortby {
|
||||
@ -116,34 +117,72 @@ func organizeFiles(fi []os.FileInfo) []os.FileInfo {
|
||||
return fi
|
||||
}
|
||||
|
||||
func newDir(path string) *Dir {
|
||||
fi, err := ioutil.ReadDir(path)
|
||||
func readdir(path string) ([]*File, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
log.Printf("reading directory: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi = organizeFiles(fi)
|
||||
names, err := f.Readdirnames(-1)
|
||||
fi := make([]*File, 0, len(names))
|
||||
for _, filename := range names {
|
||||
if !gOpts.hidden && filename[0] == '.' {
|
||||
continue
|
||||
}
|
||||
|
||||
fpath := filepath.Join(path, filename)
|
||||
|
||||
lstat, lerr := os.Lstat(fpath)
|
||||
if os.IsNotExist(lerr) {
|
||||
continue
|
||||
}
|
||||
if lerr != nil {
|
||||
return fi, lerr
|
||||
}
|
||||
|
||||
var linkState LinkState
|
||||
|
||||
if lstat.Mode()&os.ModeSymlink != 0 {
|
||||
stat, serr := os.Stat(fpath)
|
||||
if serr == nil {
|
||||
linkState = Working
|
||||
lstat = stat
|
||||
} else {
|
||||
linkState = Broken
|
||||
log.Printf("getting link destination info: %s", serr)
|
||||
}
|
||||
}
|
||||
|
||||
fi = append(fi, &File{
|
||||
FileInfo: lstat,
|
||||
LinkState: linkState,
|
||||
Path: fpath,
|
||||
})
|
||||
}
|
||||
return fi, err
|
||||
}
|
||||
|
||||
type Dir struct {
|
||||
ind int // which entry is highlighted
|
||||
pos int // which line in the ui highlighted entry is
|
||||
path string
|
||||
fi []*File
|
||||
}
|
||||
|
||||
func newDir(path string) *Dir {
|
||||
return &Dir{
|
||||
path: path,
|
||||
fi: fi,
|
||||
fi: getFilesSorted(path),
|
||||
}
|
||||
}
|
||||
|
||||
func (dir *Dir) renew(height int) {
|
||||
fi, err := ioutil.ReadDir(dir.path)
|
||||
if err != nil {
|
||||
log.Printf("reading directory: %s", err)
|
||||
}
|
||||
|
||||
fi = organizeFiles(fi)
|
||||
|
||||
var name string
|
||||
if len(dir.fi) != 0 {
|
||||
name = dir.fi[dir.ind].Name()
|
||||
}
|
||||
|
||||
dir.fi = fi
|
||||
dir.fi = getFilesSorted(dir.path)
|
||||
|
||||
dir.load(dir.ind, dir.pos, height, name)
|
||||
}
|
||||
@ -296,8 +335,17 @@ func (nav *Nav) updir() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrNotDir = fmt.Errorf("not a directory")
|
||||
|
||||
func (nav *Nav) open() error {
|
||||
path := nav.currPath()
|
||||
curr, err := nav.currFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !curr.IsDir() {
|
||||
return ErrNotDir
|
||||
}
|
||||
path := curr.Path
|
||||
|
||||
dir := newDir(path)
|
||||
|
||||
@ -354,11 +402,12 @@ func (nav *Nav) toggleMark(path string) {
|
||||
}
|
||||
|
||||
func (nav *Nav) toggle() {
|
||||
if nav.currEmpty() {
|
||||
curr, err := nav.currFile()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nav.toggleMark(nav.currPath())
|
||||
nav.toggleMark(curr.Path)
|
||||
|
||||
nav.down(1)
|
||||
}
|
||||
@ -373,13 +422,12 @@ func (nav *Nav) invert() {
|
||||
|
||||
func (nav *Nav) save(keep bool) error {
|
||||
if len(nav.marks) == 0 {
|
||||
if nav.currEmpty() {
|
||||
curr, err := nav.currFile()
|
||||
if err != nil {
|
||||
return errors.New("no file selected")
|
||||
}
|
||||
|
||||
path := nav.currPath()
|
||||
|
||||
if err := saveFiles([]string{path}, keep); err != nil {
|
||||
if err := saveFiles([]string{curr.Path}, keep); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@ -433,23 +481,17 @@ func (nav *Nav) paste() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nav *Nav) currEmpty() bool {
|
||||
return len(nav.dirs[len(nav.dirs)-1].fi) == 0
|
||||
}
|
||||
|
||||
func (nav *Nav) currDir() *Dir {
|
||||
return nav.dirs[len(nav.dirs)-1]
|
||||
}
|
||||
|
||||
func (nav *Nav) currFile() os.FileInfo {
|
||||
func (nav *Nav) currFile() (*File, error) {
|
||||
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]
|
||||
return filepath.Join(last.path, curr.Name())
|
||||
if len(last.fi) == 0 {
|
||||
return nil, fmt.Errorf("empty directory")
|
||||
}
|
||||
return last.fi[last.ind], nil
|
||||
}
|
||||
|
||||
func (nav *Nav) currMarks() []string {
|
||||
|
54
ui.go
54
ui.go
@ -236,6 +236,15 @@ func (win *Win) printd(dir *Dir, marks map[string]bool) {
|
||||
|
||||
for i, f := range dir.fi[beg:end] {
|
||||
switch {
|
||||
case f.LinkState != NotLink:
|
||||
if f.LinkState == Working {
|
||||
fg = termbox.ColorCyan
|
||||
if f.Mode().IsDir() {
|
||||
fg |= termbox.AttrBold
|
||||
}
|
||||
} else {
|
||||
fg = termbox.ColorMagenta
|
||||
}
|
||||
case f.Mode().IsRegular():
|
||||
if f.Mode()&0111 != 0 {
|
||||
fg = termbox.AttrBold | termbox.ColorGreen
|
||||
@ -244,8 +253,6 @@ func (win *Win) printd(dir *Dir, marks map[string]bool) {
|
||||
}
|
||||
case f.Mode().IsDir():
|
||||
fg = termbox.AttrBold | termbox.ColorBlue
|
||||
case f.Mode()&os.ModeSymlink != 0:
|
||||
fg = termbox.ColorCyan
|
||||
case f.Mode()&os.ModeNamedPipe != 0:
|
||||
fg = termbox.ColorRed
|
||||
case f.Mode()&os.ModeSocket != 0:
|
||||
@ -390,39 +397,26 @@ func (ui *UI) renew() {
|
||||
}
|
||||
|
||||
func (ui *UI) loadFile(nav *Nav) {
|
||||
dir := nav.currDir()
|
||||
|
||||
if len(dir.fi) == 0 {
|
||||
curr, err := nav.currFile()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
curr := nav.currFile()
|
||||
|
||||
ui.message = fmt.Sprintf("%v %v %v", curr.Mode(), humanize(curr.Size()), curr.ModTime().Format(time.ANSIC))
|
||||
|
||||
if !gOpts.preview {
|
||||
return
|
||||
}
|
||||
|
||||
path := nav.currPath()
|
||||
|
||||
f, err := os.Stat(path)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("getting file information: %s", err)
|
||||
ui.message = msg
|
||||
log.Print(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
dir := newDir(path)
|
||||
dir.load(nav.inds[path], nav.poss[path], nav.height, nav.names[path])
|
||||
if curr.IsDir() {
|
||||
dir := newDir(curr.Path)
|
||||
dir.load(nav.inds[curr.Path], nav.poss[curr.Path], nav.height, nav.names[curr.Path])
|
||||
ui.dirprev = dir
|
||||
} else if f.Mode().IsRegular() {
|
||||
} else if curr.Mode().IsRegular() {
|
||||
var reader io.Reader
|
||||
|
||||
if len(gOpts.previewer) != 0 {
|
||||
cmd := exec.Command(gOpts.previewer, path, strconv.Itoa(nav.height))
|
||||
cmd := exec.Command(gOpts.previewer, curr.Path, strconv.Itoa(nav.height))
|
||||
|
||||
out, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@ -441,7 +435,7 @@ func (ui *UI) loadFile(nav *Nav) {
|
||||
defer out.Close()
|
||||
reader = out
|
||||
} else {
|
||||
f, err := os.Open(path)
|
||||
f, err := os.Open(curr.Path)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("opening file: %s", err)
|
||||
ui.message = msg
|
||||
@ -489,7 +483,7 @@ func (ui *UI) draw(nav *Nav) {
|
||||
// leave the cursor at the beginning of the current file for screen readers
|
||||
var length, woff, doff int
|
||||
defer func() {
|
||||
fmt.Printf("[%d;%dH", ui.wins[woff+length-1].y + nav.dirs[doff+length-1].pos + 1, ui.wins[woff+length-1].x + 1)
|
||||
fmt.Printf("[%d;%dH", ui.wins[woff+length-1].y+nav.dirs[doff+length-1].pos+1, ui.wins[woff+length-1].x+1)
|
||||
}()
|
||||
|
||||
defer termbox.Flush()
|
||||
@ -519,20 +513,12 @@ func (ui *UI) draw(nav *Nav) {
|
||||
defer ui.msgwin.print(0, 0, fg, bg, ui.message)
|
||||
|
||||
if gOpts.preview {
|
||||
if len(dir.fi) == 0 {
|
||||
f, err := nav.currFile()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
preview := ui.wins[len(ui.wins)-1]
|
||||
path := nav.currPath()
|
||||
|
||||
f, err := os.Stat(path)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("getting file information: %s", err)
|
||||
ui.message = msg
|
||||
log.Print(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
preview.printd(ui.dirprev, nav.marks)
|
||||
|
Loading…
Reference in New Issue
Block a user