diff --git a/app.go b/app.go index 434f24b..c07f07a 100644 --- a/app.go +++ b/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() diff --git a/eval.go b/eval.go index 2f66815..6facc28 100644 --- a/eval.go +++ b/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 { - 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) diff --git a/nav.go b/nav.go index a0fca04..5e9b9aa 100644 --- a/nav.go +++ b/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 { diff --git a/ui.go b/ui.go index 32d5e1e..41def4e 100644 --- a/ui.go +++ b/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)