diff --git a/app.go b/app.go index 751295cc..d757c298 100644 --- a/app.go +++ b/app.go @@ -32,6 +32,24 @@ type app struct { cmdHistoryInd int } +var gInvalidate struct { + sort bool + pos bool + dir bool + navSize bool + mouse bool + period bool +} + +func init() { + gInvalidate.sort = true + gInvalidate.pos = true + gInvalidate.dir = true + gInvalidate.navSize = true + gInvalidate.mouse = true + gInvalidate.period = true +} + func newApp(ui *ui, nav *nav) *app { quitChan := make(chan struct{}, 1) @@ -245,19 +263,6 @@ func (app *app) loop() { app.ui.readExpr() - if gSelect != "" { - go func() { - lstat, err := os.Lstat(gSelect) - if err != nil { - app.ui.exprChan <- &callExpr{"echoerr", []string{err.Error()}, 1} - } else if lstat.IsDir() { - app.ui.exprChan <- &callExpr{"cd", []string{gSelect}, 1} - } else { - app.ui.exprChan <- &callExpr{"select", []string{gSelect}, 1} - } - }() - } - if gConfigPath != "" { if _, err := os.Stat(gConfigPath); !os.IsNotExist(err) { app.readFile(gConfigPath) @@ -272,6 +277,30 @@ func (app *app) loop() { } } + // config has been read, now initialize nav with the wd + wd, err := os.Getwd() + if err != nil { + log.Printf("getting current directory: %s", err) + } + if gSelect != "" { + _, err := os.Lstat(gSelect) + if err != nil { + app.ui.exprChan <- &callExpr{"echoerr", []string{err.Error()}, 1} + } else if abs, err := filepath.Abs(gSelect); err == nil { + // if gSelect contains the /. suffix, the directory itself + // should be selected + if len(gSelect) > 2 && gSelect[len(gSelect)-2:] == "/." { + wd = abs + } else { + wd = filepath.Dir(abs) + app.ui.exprChan <- &callExpr{"select", []string{abs}, 1} + } + } + } + app.nav.getDirs(wd) + app.nav.addJumpList() + + // execute commands from args for _, cmd := range gCommands { p := newParser(strings.NewReader(cmd)) @@ -285,6 +314,49 @@ func (app *app) loop() { } for { + + // process invalidate flags + if gInvalidate.sort { + app.nav.sort() + app.ui.sort() + gInvalidate.sort = false + } + if gInvalidate.pos { + app.nav.position() + gInvalidate.pos = false + } + if gInvalidate.dir { + app.ui.loadFile(app.nav, true) + app.ui.loadFileInfo(app.nav) + app.ui.draw(app.nav) + gInvalidate.dir = false + } + if gInvalidate.navSize { + app.ui.renew() + if app.nav.height != app.ui.wins[0].h { + app.nav.height = app.ui.wins[0].h + app.nav.regCache = make(map[string]*reg) + } + gInvalidate.navSize = false + } + if gInvalidate.mouse { + if gOpts.mouse { + app.ui.screen.EnableMouse() + } else { + app.ui.screen.DisableMouse() + } + gInvalidate.mouse = false + } + if gInvalidate.period { + if gOpts.period == 0 { + app.ticker.Stop() + } else { + app.ticker.Stop() + app.ticker = time.NewTicker(time.Duration(gOpts.period) * time.Second) + } + gInvalidate.period = false + } + select { case <-app.quitChan: if app.nav.copyTotal > 0 { @@ -369,7 +441,12 @@ func (app *app) loop() { } app.ui.draw(app.nav) case d := <-app.nav.dirChan: - app.nav.checkDir(d) + + if !app.nav.checkDir(d) { + log.Printf("debug: dirChan skip/reload %s", d.path) + continue + } + log.Printf("debug: dirChan %s %t", d.path, d.loading) if gOpts.dircache { prev, ok := app.nav.dirCache[d.path] diff --git a/eval.go b/eval.go index bb9fe62c..c98bcd5b 100644 --- a/eval.go +++ b/eval.go @@ -7,12 +7,12 @@ import ( "path/filepath" "strconv" "strings" - "time" "unicode" "unicode/utf8" ) func (e *setExpr) eval(app *app, args []string) { + // from app only app.ui.echo*() may be used inside this function switch e.opt { case "anchorfind": gOpts.anchorfind = true @@ -40,91 +40,70 @@ func (e *setExpr) eval(app *app, args []string) { gOpts.dircounts = !gOpts.dircounts case "dironly": gOpts.dironly = true - app.nav.sort() - app.nav.position() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.pos = true + gInvalidate.dir = true case "nodironly": gOpts.dironly = false - app.nav.sort() - app.nav.position() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.pos = true + gInvalidate.dir = true case "dironly!": gOpts.dironly = !gOpts.dironly - app.nav.sort() - app.nav.position() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.pos = true + gInvalidate.dir = true case "dirfirst": gOpts.sortType.option |= dirfirstSort - app.nav.sort() - app.ui.sort() + gInvalidate.sort = true + gInvalidate.dir = true case "nodirfirst": gOpts.sortType.option &= ^dirfirstSort - app.nav.sort() - app.ui.sort() + gInvalidate.sort = true + gInvalidate.dir = true case "dirfirst!": gOpts.sortType.option ^= dirfirstSort - app.nav.sort() - app.ui.sort() + gInvalidate.sort = true + gInvalidate.dir = true case "drawbox": gOpts.drawbox = true - app.ui.renew() - if app.nav.height != app.ui.wins[0].h { - app.nav.height = app.ui.wins[0].h - app.nav.regCache = make(map[string]*reg) - } - app.ui.loadFile(app.nav, true) + gInvalidate.navSize = true + gInvalidate.dir = true case "nodrawbox": gOpts.drawbox = false - app.ui.renew() - if app.nav.height != app.ui.wins[0].h { - app.nav.height = app.ui.wins[0].h - app.nav.regCache = make(map[string]*reg) - } - app.ui.loadFile(app.nav, true) + gInvalidate.navSize = true + gInvalidate.dir = true case "drawbox!": gOpts.drawbox = !gOpts.drawbox - app.ui.renew() - if app.nav.height != app.ui.wins[0].h { - app.nav.height = app.ui.wins[0].h - app.nav.regCache = make(map[string]*reg) - } - app.ui.loadFile(app.nav, true) + gInvalidate.navSize = true + gInvalidate.dir = true case "globsearch": gOpts.globsearch = true - app.nav.sort() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.dir = true case "noglobsearch": gOpts.globsearch = false - app.nav.sort() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.dir = true case "globsearch!": gOpts.globsearch = !gOpts.globsearch - app.nav.sort() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.dir = true case "hidden": gOpts.sortType.option |= hiddenSort - app.nav.sort() - app.nav.position() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.pos = true + gInvalidate.dir = true case "nohidden": gOpts.sortType.option &= ^hiddenSort - app.nav.sort() - app.nav.position() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.pos = true + gInvalidate.dir = true case "hidden!": gOpts.sortType.option ^= hiddenSort - app.nav.sort() - app.nav.position() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.pos = true + gInvalidate.dir = true case "icons": gOpts.icons = true case "noicons": @@ -133,31 +112,28 @@ func (e *setExpr) eval(app *app, args []string) { gOpts.icons = !gOpts.icons case "ignorecase": gOpts.ignorecase = true - app.nav.sort() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.dir = true case "noignorecase": gOpts.ignorecase = false - app.nav.sort() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.dir = true case "ignorecase!": gOpts.ignorecase = !gOpts.ignorecase - app.nav.sort() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.dir = true case "ignoredia": gOpts.ignoredia = true - app.nav.sort() - app.ui.sort() + gInvalidate.sort = true + gInvalidate.dir = true case "noignoredia": gOpts.ignoredia = false - app.nav.sort() - app.ui.sort() + gInvalidate.sort = true + gInvalidate.dir = true case "ignoredia!": gOpts.ignoredia = !gOpts.ignoredia - app.nav.sort() - app.ui.sort() + gInvalidate.sort = true + gInvalidate.dir = true case "incfilter": gOpts.incfilter = true case "noincfilter": @@ -173,21 +149,20 @@ func (e *setExpr) eval(app *app, args []string) { case "mouse": if !gOpts.mouse { gOpts.mouse = true - app.ui.screen.EnableMouse() + gInvalidate.mouse = true } case "nomouse": if gOpts.mouse { gOpts.mouse = false - app.ui.screen.DisableMouse() + gInvalidate.mouse = true } case "mouse!": if gOpts.mouse { gOpts.mouse = false - app.ui.screen.DisableMouse() } else { gOpts.mouse = true - app.ui.screen.EnableMouse() } + gInvalidate.mouse = true case "number": gOpts.number = true case "nonumber": @@ -216,31 +191,28 @@ func (e *setExpr) eval(app *app, args []string) { gOpts.relativenumber = !gOpts.relativenumber case "reverse": gOpts.sortType.option |= reverseSort - app.nav.sort() - app.ui.sort() + gInvalidate.sort = true + gInvalidate.dir = true case "noreverse": gOpts.sortType.option &= ^reverseSort - app.nav.sort() - app.ui.sort() + gInvalidate.sort = true + gInvalidate.dir = true case "reverse!": gOpts.sortType.option ^= reverseSort - app.nav.sort() - app.ui.sort() + gInvalidate.sort = true + gInvalidate.dir = true case "smartcase": gOpts.smartcase = true - app.nav.sort() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.dir = true case "nosmartcase": gOpts.smartcase = false - app.nav.sort() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.dir = true case "smartcase!": gOpts.smartcase = !gOpts.smartcase - app.nav.sort() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.dir = true case "smartdia": gOpts.smartdia = true case "nosmartdia": @@ -283,12 +255,7 @@ func (e *setExpr) eval(app *app, args []string) { return } gOpts.period = n - if n == 0 { - app.ticker.Stop() - } else { - app.ticker.Stop() - app.ticker = time.NewTicker(time.Duration(gOpts.period) * time.Second) - } + gInvalidate.period = true case "scrolloff": n, err := strconv.Atoi(e.val) if err != nil { @@ -329,10 +296,9 @@ func (e *setExpr) eval(app *app, args []string) { } } gOpts.hiddenfiles = toks - app.nav.sort() - app.nav.position() - app.ui.sort() - app.ui.loadFile(app.nav, true) + gInvalidate.sort = true + gInvalidate.pos = true + gInvalidate.dir = true case "ifs": gOpts.ifs = e.val case "info": @@ -377,7 +343,7 @@ func (e *setExpr) eval(app *app, args []string) { } gOpts.ratios = rats app.ui.wins = getWins(app.ui.screen) - app.ui.loadFile(app.nav, true) + gInvalidate.dir = true case "shell": gOpts.shell = e.val case "shellflag": @@ -408,8 +374,8 @@ func (e *setExpr) eval(app *app, args []string) { app.ui.echoerr("sortby: value should either be 'natural', 'name', 'size', 'time', 'atime', 'ctime' or 'ext'") return } - app.nav.sort() - app.ui.sort() + gInvalidate.sort = true + gInvalidate.dir = true case "tempmarks": if e.val != "" { gOpts.tempmarks = "'" + e.val @@ -433,7 +399,6 @@ func (e *setExpr) eval(app *app, args []string) { app.ui.echoerrf("unknown option: %s", e.opt) return } - app.ui.loadFileInfo(app.nav) } func (e *mapExpr) eval(app *app, args []string) { @@ -442,7 +407,6 @@ func (e *mapExpr) eval(app *app, args []string) { } else { gOpts.keys[e.keys] = e.expr } - app.ui.loadFileInfo(app.nav) } func (e *cmapExpr) eval(app *app, args []string) { @@ -451,7 +415,6 @@ func (e *cmapExpr) eval(app *app, args []string) { } else { gOpts.cmdkeys[e.key] = e.expr } - app.ui.loadFileInfo(app.nav) } func (e *cmdExpr) eval(app *app, args []string) { @@ -460,7 +423,6 @@ func (e *cmdExpr) eval(app *app, args []string) { } else { gOpts.cmds[e.name] = e.expr } - app.ui.loadFileInfo(app.nav) } func preChdir(app *app) { @@ -1294,6 +1256,7 @@ func (e *callExpr) eval(app *app, args []string) { if err != nil { log.Printf("getting current directory: %s", err) } + log.Printf("todo-cd-eval %s", wd) path = replaceTilde(path) if !filepath.IsAbs(path) { diff --git a/nav.go b/nav.go index 12c7568e..6d1c294b 100644 --- a/nav.go +++ b/nav.go @@ -163,6 +163,10 @@ func normalize(s1, s2 string, ignorecase, ignoredia bool) (string, string) { } func (dir *dir) sort() { + if dir.loading { + log.Printf("debug: sort/dir still loading: %s", dir.path) + return + } dir.sortType = gOpts.sortType dir.dironly = gOpts.dironly dir.hiddenfiles = gOpts.hiddenfiles @@ -306,6 +310,10 @@ func (dir *dir) name() string { } func (dir *dir) sel(name string, height int) { + if dir.loading { + //log.Printf("debug: sel/dir still loading: %s", dir.path) + return + } if len(dir.files) == 0 { dir.ind, dir.pos = 0, 0 return @@ -391,11 +399,13 @@ func (nav *nav) loadDir(path string) *dir { if gOpts.dircache { d, ok := nav.dirCache[path] if !ok { + log.Printf("debug: dirCache-new: %s", path) d = nav.loadDirInternal(path) nav.dirCache[path] = d return d } + log.Printf("debug: dirCache-found: %s", path) nav.checkDir(d) return d @@ -404,11 +414,16 @@ func (nav *nav) loadDir(path string) *dir { } } -func (nav *nav) checkDir(dir *dir) { +func (nav *nav) checkDir(dir *dir) bool { + if dir.loading { + log.Printf("debug: skip loading: %s", dir.path) + return false + } + s, err := os.Stat(dir.path) if err != nil { log.Printf("getting directory info: %s", err) - return + return true } switch { @@ -418,9 +433,10 @@ func (nav *nav) checkDir(dir *dir) { // XXX: Linux builtin exFAT drivers are able to predict modifications in the future // https://bugs.launchpad.net/ubuntu/+source/ubuntu-meta/+bug/1872504 if s.ModTime().After(now) { - return + return true } + log.Printf("debug: checkDir-reload: %s time", dir.path) dir.loading = true dir.loadTime = now go func() { @@ -429,18 +445,23 @@ func (nav *nav) checkDir(dir *dir) { nd.sort() nav.dirChan <- nd }() + return false case dir.sortType != gOpts.sortType || dir.dironly != gOpts.dironly || !reflect.DeepEqual(dir.hiddenfiles, gOpts.hiddenfiles) || dir.ignorecase != gOpts.ignorecase || dir.ignoredia != gOpts.ignoredia: + log.Printf("debug: checkDir-reload: %s opt", dir.path) + sd := dir dir.loading = true + dir.loadTime = time.Now() go func() { - dir.sort() - dir.loading = false - nav.dirChan <- dir + sd.sort() + nav.dirChan <- sd }() + return false } + return true } func (nav *nav) getDirs(wd string) { @@ -460,10 +481,6 @@ func (nav *nav) getDirs(wd string) { } func newNav(height int) *nav { - wd, err := os.Getwd() - if err != nil { - log.Printf("getting current directory: %s", err) - } nav := &nav{ copyBytesChan: make(chan int64, 1024), @@ -486,8 +503,7 @@ func newNav(height int) *nav { jumpListInd: -1, } - nav.getDirs(wd) - nav.addJumpList() + // do not call nav.getDirs() as our configuration isn't set up yet return nav } @@ -573,7 +589,14 @@ func (nav *nav) exportFiles() { currSelections := nav.currSelections() - exportFiles(currFile, currSelections, nav.currDir().path) + var wd string + if currDir := nav.currDir(); currDir != nil { + wd = currDir.path + } else { + wd, _ = os.Getwd() + } + + exportFiles(currFile, currSelections, wd) } func (nav *nav) previewLoop(ui *ui) { @@ -1252,19 +1275,26 @@ func (nav *nav) sync() error { } func (nav *nav) cd(wd string) error { + + currDir := nav.currDir() + wd = replaceTilde(wd) wd = filepath.Clean(wd) - if !filepath.IsAbs(wd) { - wd = filepath.Join(nav.currDir().path, wd) + if !filepath.IsAbs(wd) && currDir != nil { + wd = filepath.Join(currDir.path, wd) } if err := os.Chdir(wd); err != nil { return fmt.Errorf("cd: %s", err) } - nav.getDirs(wd) - nav.addJumpList() + if currDir == nil || wd != currDir.path { + nav.getDirs(wd) + nav.addJumpList() + } else { + log.Printf("debug: skip cd: %s", wd) + } return nil } @@ -1542,13 +1572,16 @@ func (nav *nav) writeMarks() error { } func (nav *nav) currDir() *dir { + if len(nav.dirs) == 0 { + return nil + } return nav.dirs[len(nav.dirs)-1] } func (nav *nav) currFile() (*file, error) { - dir := nav.dirs[len(nav.dirs)-1] + dir := nav.currDir() - if len(dir.files) == 0 { + if dir == nil || len(dir.files) == 0 { return nil, fmt.Errorf("empty directory") }