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() {
|
func (app *App) exportVars() {
|
||||||
dir := app.nav.currDir()
|
|
||||||
|
|
||||||
var envFile string
|
var envFile string
|
||||||
if len(dir.fi) != 0 {
|
if f, err := app.nav.currFile(); err == nil {
|
||||||
envFile = app.nav.currPath()
|
envFile = f.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
marks := app.nav.currMarks()
|
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)
|
app.ui.loadFile(app.nav)
|
||||||
case "open":
|
case "open":
|
||||||
dir := app.nav.currDir()
|
err := app.nav.open()
|
||||||
|
if err == nil {
|
||||||
if len(dir.fi) == 0 {
|
app.ui.loadFile(app.nav)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err != ErrNotDir {
|
||||||
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()
|
app.ui.message = err.Error()
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.ui.loadFile(app.nav)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if gSelectionPath != "" {
|
if gSelectionPath != "" {
|
||||||
out, err := os.Create(gSelectionPath)
|
out, err := os.Create(gSelectionPath)
|
||||||
@ -211,9 +196,14 @@ func (e *CallExpr) eval(app *App, args []string) {
|
|||||||
}
|
}
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
|
|
||||||
|
var path string
|
||||||
if len(app.nav.marks) != 0 {
|
if len(app.nav.marks) != 0 {
|
||||||
marks := app.nav.currMarks()
|
marks := app.nav.currMarks()
|
||||||
path = strings.Join(marks, "\n")
|
path = strings.Join(marks, "\n")
|
||||||
|
} else if curr, err := app.nav.currFile(); err == nil {
|
||||||
|
path = curr.Path
|
||||||
|
} else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = out.WriteString(path)
|
_, err = out.WriteString(path)
|
||||||
|
144
nav.go
144
nav.go
@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -12,14 +11,21 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dir struct {
|
type LinkState int8
|
||||||
ind int // which entry is highlighted
|
|
||||||
pos int // which line in the ui highlighted entry is
|
const (
|
||||||
path string
|
NotLink LinkState = iota
|
||||||
fi []os.FileInfo
|
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) Len() int { return len(a) }
|
||||||
func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
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())
|
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) Len() int { return len(a) }
|
||||||
func (a BySize) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
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() }
|
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) Len() int { return len(a) }
|
||||||
func (a ByTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
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()) }
|
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) Len() int { return len(a) }
|
||||||
func (a ByDir) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
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()
|
return a[i].IsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ByNum []os.FileInfo
|
type ByNum []*File
|
||||||
|
|
||||||
func (a ByNum) Len() int { return len(a) }
|
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) 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
|
return i < j
|
||||||
}
|
}
|
||||||
|
|
||||||
func organizeFiles(fi []os.FileInfo) []os.FileInfo {
|
func getFilesSorted(path string) []*File {
|
||||||
if !gOpts.hidden {
|
fi, err := readdir(path)
|
||||||
var tmp []os.FileInfo
|
if err != nil {
|
||||||
for _, f := range fi {
|
log.Printf("reading directory: %s", err)
|
||||||
if f.Name()[0] != '.' {
|
|
||||||
tmp = append(tmp, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fi = tmp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch gOpts.sortby {
|
switch gOpts.sortby {
|
||||||
@ -116,34 +117,72 @@ func organizeFiles(fi []os.FileInfo) []os.FileInfo {
|
|||||||
return fi
|
return fi
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDir(path string) *Dir {
|
func readdir(path string) ([]*File, error) {
|
||||||
fi, err := ioutil.ReadDir(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
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{
|
return &Dir{
|
||||||
path: path,
|
path: path,
|
||||||
fi: fi,
|
fi: getFilesSorted(path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dir *Dir) renew(height int) {
|
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
|
var name string
|
||||||
if len(dir.fi) != 0 {
|
if len(dir.fi) != 0 {
|
||||||
name = dir.fi[dir.ind].Name()
|
name = dir.fi[dir.ind].Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
dir.fi = fi
|
dir.fi = getFilesSorted(dir.path)
|
||||||
|
|
||||||
dir.load(dir.ind, dir.pos, height, name)
|
dir.load(dir.ind, dir.pos, height, name)
|
||||||
}
|
}
|
||||||
@ -296,8 +335,17 @@ func (nav *Nav) updir() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrNotDir = fmt.Errorf("not a directory")
|
||||||
|
|
||||||
func (nav *Nav) open() error {
|
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)
|
dir := newDir(path)
|
||||||
|
|
||||||
@ -354,11 +402,12 @@ func (nav *Nav) toggleMark(path string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (nav *Nav) toggle() {
|
func (nav *Nav) toggle() {
|
||||||
if nav.currEmpty() {
|
curr, err := nav.currFile()
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.toggleMark(nav.currPath())
|
nav.toggleMark(curr.Path)
|
||||||
|
|
||||||
nav.down(1)
|
nav.down(1)
|
||||||
}
|
}
|
||||||
@ -373,13 +422,12 @@ func (nav *Nav) invert() {
|
|||||||
|
|
||||||
func (nav *Nav) save(keep bool) error {
|
func (nav *Nav) save(keep bool) error {
|
||||||
if len(nav.marks) == 0 {
|
if len(nav.marks) == 0 {
|
||||||
if nav.currEmpty() {
|
curr, err := nav.currFile()
|
||||||
|
if err != nil {
|
||||||
return errors.New("no file selected")
|
return errors.New("no file selected")
|
||||||
}
|
}
|
||||||
|
|
||||||
path := nav.currPath()
|
if err := saveFiles([]string{curr.Path}, keep); err != nil {
|
||||||
|
|
||||||
if err := saveFiles([]string{path}, keep); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -433,23 +481,17 @@ func (nav *Nav) paste() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nav *Nav) currEmpty() bool {
|
|
||||||
return len(nav.dirs[len(nav.dirs)-1].fi) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nav *Nav) currDir() *Dir {
|
func (nav *Nav) currDir() *Dir {
|
||||||
return nav.dirs[len(nav.dirs)-1]
|
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]
|
last := nav.dirs[len(nav.dirs)-1]
|
||||||
return last.fi[last.ind]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nav *Nav) currPath() string {
|
if len(last.fi) == 0 {
|
||||||
last := nav.dirs[len(nav.dirs)-1]
|
return nil, fmt.Errorf("empty directory")
|
||||||
curr := last.fi[last.ind]
|
}
|
||||||
return filepath.Join(last.path, curr.Name())
|
return last.fi[last.ind], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nav *Nav) currMarks() []string {
|
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] {
|
for i, f := range dir.fi[beg:end] {
|
||||||
switch {
|
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():
|
case f.Mode().IsRegular():
|
||||||
if f.Mode()&0111 != 0 {
|
if f.Mode()&0111 != 0 {
|
||||||
fg = termbox.AttrBold | termbox.ColorGreen
|
fg = termbox.AttrBold | termbox.ColorGreen
|
||||||
@ -244,8 +253,6 @@ func (win *Win) printd(dir *Dir, marks map[string]bool) {
|
|||||||
}
|
}
|
||||||
case f.Mode().IsDir():
|
case f.Mode().IsDir():
|
||||||
fg = termbox.AttrBold | termbox.ColorBlue
|
fg = termbox.AttrBold | termbox.ColorBlue
|
||||||
case f.Mode()&os.ModeSymlink != 0:
|
|
||||||
fg = termbox.ColorCyan
|
|
||||||
case f.Mode()&os.ModeNamedPipe != 0:
|
case f.Mode()&os.ModeNamedPipe != 0:
|
||||||
fg = termbox.ColorRed
|
fg = termbox.ColorRed
|
||||||
case f.Mode()&os.ModeSocket != 0:
|
case f.Mode()&os.ModeSocket != 0:
|
||||||
@ -390,39 +397,26 @@ func (ui *UI) renew() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ui *UI) loadFile(nav *Nav) {
|
func (ui *UI) loadFile(nav *Nav) {
|
||||||
dir := nav.currDir()
|
curr, err := nav.currFile()
|
||||||
|
if err != nil {
|
||||||
if len(dir.fi) == 0 {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
curr := nav.currFile()
|
|
||||||
|
|
||||||
ui.message = fmt.Sprintf("%v %v %v", curr.Mode(), humanize(curr.Size()), curr.ModTime().Format(time.ANSIC))
|
ui.message = fmt.Sprintf("%v %v %v", curr.Mode(), humanize(curr.Size()), curr.ModTime().Format(time.ANSIC))
|
||||||
|
|
||||||
if !gOpts.preview {
|
if !gOpts.preview {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
path := nav.currPath()
|
if curr.IsDir() {
|
||||||
|
dir := newDir(curr.Path)
|
||||||
f, err := os.Stat(path)
|
dir.load(nav.inds[curr.Path], nav.poss[curr.Path], nav.height, nav.names[curr.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])
|
|
||||||
ui.dirprev = dir
|
ui.dirprev = dir
|
||||||
} else if f.Mode().IsRegular() {
|
} else if curr.Mode().IsRegular() {
|
||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
|
|
||||||
if len(gOpts.previewer) != 0 {
|
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()
|
out, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -441,7 +435,7 @@ func (ui *UI) loadFile(nav *Nav) {
|
|||||||
defer out.Close()
|
defer out.Close()
|
||||||
reader = out
|
reader = out
|
||||||
} else {
|
} else {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(curr.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := fmt.Sprintf("opening file: %s", err)
|
msg := fmt.Sprintf("opening file: %s", err)
|
||||||
ui.message = msg
|
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
|
// leave the cursor at the beginning of the current file for screen readers
|
||||||
var length, woff, doff int
|
var length, woff, doff int
|
||||||
defer func() {
|
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()
|
defer termbox.Flush()
|
||||||
@ -519,20 +513,12 @@ func (ui *UI) draw(nav *Nav) {
|
|||||||
defer ui.msgwin.print(0, 0, fg, bg, ui.message)
|
defer ui.msgwin.print(0, 0, fg, bg, ui.message)
|
||||||
|
|
||||||
if gOpts.preview {
|
if gOpts.preview {
|
||||||
if len(dir.fi) == 0 {
|
f, err := nav.currFile()
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
preview := ui.wins[len(ui.wins)-1]
|
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() {
|
if f.IsDir() {
|
||||||
preview.printd(ui.dirprev, nav.marks)
|
preview.printd(ui.dirprev, nav.marks)
|
||||||
|
Loading…
Reference in New Issue
Block a user