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:
Karol Woźniak 2016-10-24 21:18:31 +02:00 committed by gokcehan
parent 880615ddd0
commit c9b4389c65
4 changed files with 127 additions and 111 deletions

6
app.go
View File

@ -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()

34
eval.go
View File

@ -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 {
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 {
app.ui.message = err.Error()
log.Print(err)
return
}
err := app.nav.open()
if err == nil {
app.ui.loadFile(app.nav)
return
}
if err != ErrNotDir {
app.ui.message = err.Error()
log.Print(err)
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
View File

@ -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
View File

@ -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)