From 9515bd73d0b5e454a11d8d875c242802cb9db8e8 Mon Sep 17 00:00:00 2001 From: Kennedy Mwenja Date: Wed, 18 Sep 2019 21:52:30 +0300 Subject: [PATCH] Sort By Access Time and Change Time (#226) - Add access time and change time as sort by types. This is powered by github.com/djherbis/times. - Fall back to modification time if access time and change time cannot be determined. - Add `sa` and `sc` as default bindings for sort by access time and sort by change time respectively. - Add access time and change time to info types allowing them to be displayed by the file list in the ui --- docstring.go | 2 ++ eval.go | 17 +++++++++++------ go.mod | 1 + go.sum | 2 ++ nav.go | 39 ++++++++++++++++++++++++++++++++------- opts.go | 4 ++++ ui.go | 4 ++++ 7 files changed, 56 insertions(+), 13 deletions(-) diff --git a/docstring.go b/docstring.go index 0a55faa..2132b35 100644 --- a/docstring.go +++ b/docstring.go @@ -177,6 +177,8 @@ The following additional keybindings are provided by default: map sn :set sortby natural; set info map ss :set sortby size; set info size map st :set sortby time; set info time + map sa :set sortby atime; set info atime + map sc :set sortby ctime; set info ctime map gh cd ~ The following keybindings to applications are provided by default: diff --git a/eval.go b/eval.go index 8200ada..9f4a5eb 100644 --- a/eval.go +++ b/eval.go @@ -238,10 +238,6 @@ func (e *setExpr) eval(app *app, args []string) { case "shell": gOpts.shell = e.val case "sortby": - if e.val != "natural" && e.val != "name" && e.val != "size" && e.val != "time" { - app.ui.echoerr("sortby: value should either be 'natural', 'name', 'size' or 'time'") - return - } switch e.val { case "natural": gOpts.sortType.method = naturalSort @@ -251,6 +247,13 @@ func (e *setExpr) eval(app *app, args []string) { gOpts.sortType.method = sizeSort case "time": gOpts.sortType.method = timeSort + case "ctime": + gOpts.sortType.method = ctimeSort + case "atime": + gOpts.sortType.method = atimeSort + default: + app.ui.echoerr("sortby: value should either be 'natural', 'name', 'size', 'time', 'atime' or 'ctime'") + return } app.nav.sort() app.ui.sort() @@ -281,8 +284,10 @@ func (e *setExpr) eval(app *app, args []string) { case "info": toks := strings.Split(e.val, ":") for _, s := range toks { - if s != "" && s != "size" && s != "time" { - app.ui.echoerr("info: should consist of 'size' or 'time' separated with colon") + switch s { + case "", "size", "time", "atime", "ctime": + default: + app.ui.echoerr("info: should consist of 'size', 'time', 'atime' or 'ctime' separated with colon") return } } diff --git a/go.mod b/go.mod index 86d60b6..7b68baf 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,5 @@ go 1.12 require ( github.com/mattn/go-runewidth v0.0.4 github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e + gopkg.in/djherbis/times.v1 v1.2.0 ) diff --git a/go.sum b/go.sum index 8288bd3..e8f606b 100644 --- a/go.sum +++ b/go.sum @@ -2,3 +2,5 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e h1:Vbib8wJAaMEF9jusI/kMSYMr/LtRzM7+F9MJgt/nH8k= github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= +gopkg.in/djherbis/times.v1 v1.2.0 h1:UCvDKl1L/fmBygl2Y7hubXCnY7t4Yj46ZrBFNUipFbM= +gopkg.in/djherbis/times.v1 v1.2.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8= diff --git a/nav.go b/nav.go index 4c09627..7b369c5 100644 --- a/nav.go +++ b/nav.go @@ -13,6 +13,8 @@ import ( "strconv" "strings" "time" + + times "gopkg.in/djherbis/times.v1" ) type linkState byte @@ -25,9 +27,11 @@ const ( type file struct { os.FileInfo - linkState linkState - path string - dirCount int + linkState linkState + path string + dirCount int + accessTime time.Time + changeTime time.Time } func readdir(path string) ([]*file, error) { @@ -62,11 +66,24 @@ func readdir(path string) ([]*file, error) { } } + ts := times.Get(lstat) + at := ts.AccessTime() + var ct time.Time + // from times docs: ChangeTime() panics unless HasChangeTime() is true + if ts.HasChangeTime() { + ct = ts.ChangeTime() + } else { + // fall back to ModTime if ChangeTime cannot be determined + ct = lstat.ModTime() + } + files = append(files, &file{ - FileInfo: lstat, - linkState: linkState, - path: fpath, - dirCount: -1, + FileInfo: lstat, + linkState: linkState, + path: fpath, + dirCount: -1, + accessTime: at, + changeTime: ct, }) } @@ -124,6 +141,14 @@ func (dir *dir) sort() { sort.SliceStable(dir.files, func(i, j int) bool { return dir.files[i].ModTime().Before(dir.files[j].ModTime()) }) + case atimeSort: + sort.SliceStable(dir.files, func(i, j int) bool { + return dir.files[i].accessTime.Before(dir.files[j].accessTime) + }) + case ctimeSort: + sort.SliceStable(dir.files, func(i, j int) bool { + return dir.files[i].changeTime.Before(dir.files[j].changeTime) + }) } if gOpts.sortType.option&reverseSort != 0 { diff --git a/opts.go b/opts.go index 5bf9bda..aef8c06 100644 --- a/opts.go +++ b/opts.go @@ -9,6 +9,8 @@ const ( nameSort sizeSort timeSort + atimeSort + ctimeSort ) type sortOption byte @@ -153,6 +155,8 @@ func init() { gOpts.keys["sn"] = &listExpr{[]expr{&setExpr{"sortby", "natural"}, &setExpr{"info", ""}}} gOpts.keys["ss"] = &listExpr{[]expr{&setExpr{"sortby", "size"}, &setExpr{"info", "size"}}} gOpts.keys["st"] = &listExpr{[]expr{&setExpr{"sortby", "time"}, &setExpr{"info", "time"}}} + gOpts.keys["sa"] = &listExpr{[]expr{&setExpr{"sortby", "atime"}, &setExpr{"info", "atime"}}} + gOpts.keys["sc"] = &listExpr{[]expr{&setExpr{"sortby", "ctime"}, &setExpr{"info", "ctime"}}} gOpts.keys["gh"] = &callExpr{"cd", []string{"~"}, 1} gOpts.cmdkeys = make(map[string]expr) diff --git a/ui.go b/ui.go index 5b106d4..cf9ab41 100644 --- a/ui.go +++ b/ui.go @@ -231,6 +231,10 @@ func fileInfo(f *file, d *dir) string { } case "time": info = fmt.Sprintf("%s %12s", info, f.ModTime().Format("Jan _2 15:04")) + case "atime": + info = fmt.Sprintf("%s %12s", info, f.accessTime.Format("Jan _2 15:04")) + case "ctime": + info = fmt.Sprintf("%s %12s", info, f.changeTime.Format("Jan _2 15:04")) default: log.Printf("unknown info type: %s", s) }