Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add 'setlocal' for directory specific options #1381

Merged
merged 15 commits into from
Sep 2, 2023
Merged
23 changes: 22 additions & 1 deletion complete.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
var (
gCmdWords = []string{
"set",
"setlocal",
"map",
"maps",
"cmap",
Expand Down Expand Up @@ -221,6 +222,23 @@ var (
"truncatechar",
"truncatepct",
}

gLocalOptWords = []string{
"dirfirst",
"nodirfirst",
"dirfirst!",
"dironly",
"nodironly",
"dironly!",
"hidden",
"nohidden",
"hidden!",
"info",
"reverse",
"noreverse",
"reverse!",
"sortby",
}
)

func matchLongest(s1, s2 []rune) []rune {
Expand Down Expand Up @@ -407,6 +425,9 @@ func completeCmd(acc []rune) (matches []string, longestAcc []rune) {
}
case 3:
switch f[0] {
case "setlocal":
matches, longest = matchWord(f[2], gLocalOptWords)
longestAcc = append(acc[:len(acc)-len([]rune(f[len(f)-1]))], longest...)
case "map", "cmap":
matches, longest = matchCmd(f[2])
longestAcc = append(acc[:len(acc)-len([]rune(f[len(f)-1]))], longest...)
Expand All @@ -416,7 +437,7 @@ func completeCmd(acc []rune) (matches []string, longestAcc []rune) {
}
default:
switch f[0] {
case "set", "map", "cmap", "cmd":
case "set", "setlocal", "map", "cmap", "cmd":
longestAcc = acc
default:
matches, longest = matchFile(f[len(f)-1])
Expand Down
17 changes: 16 additions & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1120,7 +1120,7 @@ Characters from '#' to newline are comments and ignored:

# comments start with '#'

There are four special commands ('set', 'map', 'cmap', and 'cmd') for configuration.
There are five special commands ('set', 'setlocal', 'map', 'cmap', and 'cmd') for configuration.

Command 'set' is used to set an option which can be boolean, integer, or string:

Expand All @@ -1134,6 +1134,21 @@ Command 'set' is used to set an option which can be boolean, integer, or string:
set sortby 'time' # string value with single quotes (whitespaces)
set sortby "time" # string value with double quotes (backslash escapes)

Command 'setlocal' is used to set a local option for a directory which can be boolean or string.
Currently supported local options are 'dirfirst', 'dironly', 'hidden', 'info', 'reverse', and 'sortby'.
Adding a trailing path separator (i.e. '/' for Unix and '\' for Windows) sets the option for the given directory along with its subdirectories:

setlocal /foo/bar hidden # boolean enable
setlocal /foo/bar hidden true # boolean enable
setlocal /foo/bar nohidden # boolean disable
setlocal /foo/bar hidden false # boolean disable
setlocal /foo/bar hidden! # boolean toggle
setlocal /foo/bar sortby time # string value w/o quotes
setlocal /foo/bar sortby 'time' # string value with single quotes (whitespaces)
setlocal /foo/bar sortby "time" # string value with double quotes (backslash escapes)
setlocal /foo/bar hidden # for only '/foo/bar' directory
setlocal /foo/bar/ hidden # for '/foo/bar' and its subdirectories (e.g. '/foo/bar/baz')

Command 'map' is used to bind a key to a command which can be builtin command, custom command, or shell command:

map gh cd ~ # builtin command
Expand Down
21 changes: 19 additions & 2 deletions docstring.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

179 changes: 179 additions & 0 deletions eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,185 @@ func (e *setExpr) eval(app *app, args []string) {
app.ui.loadFileInfo(app.nav)
}

func (e *setLocalExpr) eval(app *app, args []string) {
path := replaceTilde(e.path)
if !filepath.IsAbs(path) {
app.ui.echoerr("setlocal: path should be absolute")
return
}

switch e.opt {
case "dirfirst":
if e.val == "" || e.val == "true" {
gLocalOpts.dirfirsts[path] = true
} else if e.val == "false" {
gLocalOpts.dirfirsts[path] = false
} else {
app.ui.echoerr("dirfirst: value should be empty, 'true', or 'false'")
return
}
app.nav.sort()
app.ui.sort()
case "nodirfirst":
if e.val != "" {
app.ui.echoerrf("nodirfirst: unexpected value: %s", e.val)
return
}
gLocalOpts.dirfirsts[path] = false
app.nav.sort()
app.ui.sort()
case "dirfirst!":
if e.val != "" {
app.ui.echoerrf("dirfirst!: unexpected value: %s", e.val)
return
}
if val, ok := gLocalOpts.dirfirsts[path]; ok {
gLocalOpts.dirfirsts[path] = !val
} else {
val = gOpts.sortType.option&dirfirstSort != 0
gLocalOpts.dirfirsts[path] = !val
}
app.nav.sort()
app.ui.sort()
case "dironly":
if e.val == "" || e.val == "true" {
gLocalOpts.dironlys[path] = true
} else if e.val == "false" {
gLocalOpts.dironlys[path] = false
} else {
app.ui.echoerr("dironly: value should be empty, 'true', or 'false'")
return
}
app.nav.sort()
app.ui.sort()
case "nodironly":
if e.val != "" {
app.ui.echoerrf("nodironly: unexpected value: %s", e.val)
return
}
gLocalOpts.dironlys[path] = false
app.nav.sort()
app.ui.sort()
case "dironly!":
if e.val != "" {
app.ui.echoerrf("dironly!: unexpected value: %s", e.val)
return
}
if val, ok := gLocalOpts.dironlys[path]; ok {
gLocalOpts.dironlys[path] = !val
} else {
gLocalOpts.dironlys[path] = !gOpts.dironly
}
app.nav.sort()
app.ui.sort()
case "hidden":
if e.val == "" || e.val == "true" {
gLocalOpts.hiddens[path] = true
} else if e.val == "false" {
gLocalOpts.hiddens[path] = false
} else {
app.ui.echoerr("hidden: value should be empty, 'true', or 'false'")
return
}
app.nav.sort()
app.ui.sort()
case "nohidden":
if e.val != "" {
app.ui.echoerrf("nohidden: unexpected value: %s", e.val)
return
}
gLocalOpts.hiddens[path] = false
app.nav.sort()
app.ui.sort()
case "hidden!":
if e.val != "" {
app.ui.echoerrf("hidden!: unexpected value: %s", e.val)
return
}
if val, ok := gLocalOpts.hiddens[path]; ok {
gLocalOpts.hiddens[path] = !val
} else {
val = gOpts.sortType.option&hiddenSort != 0
gLocalOpts.hiddens[path] = !val
}
app.nav.sort()
app.ui.sort()
case "info":
if e.val == "" {
gLocalOpts.infos[path] = nil
return
}
toks := strings.Split(e.val, ":")
for _, s := range toks {
switch s {
case "size", "time", "atime", "ctime":
default:
app.ui.echoerr("info: should consist of 'size', 'time', 'atime' or 'ctime' separated with colon")
return
}
}
gLocalOpts.infos[path] = toks
case "reverse":
if e.val == "" || e.val == "true" {
gLocalOpts.reverses[path] = true
} else if e.val == "false" {
gLocalOpts.reverses[path] = false
} else {
app.ui.echoerr("reverse: value should be empty, 'true', or 'false'")
return
}
app.nav.sort()
app.ui.sort()
case "noreverse":
if e.val != "" {
app.ui.echoerrf("noreverse: unexpected value: %s", e.val)
return
}
gLocalOpts.reverses[path] = false
app.nav.sort()
app.ui.sort()
case "reverse!":
if e.val != "" {
app.ui.echoerrf("reverse!: unexpected value: %s", e.val)
return
}
if val, ok := gLocalOpts.reverses[path]; ok {
gLocalOpts.reverses[path] = !val
} else {
val = gOpts.sortType.option&reverseSort != 0
gLocalOpts.reverses[path] = !val
}
app.nav.sort()
app.ui.sort()
case "sortby":
switch e.val {
case "natural":
gLocalOpts.sortMethods[path] = naturalSort
case "name":
gLocalOpts.sortMethods[path] = nameSort
case "size":
gLocalOpts.sortMethods[path] = sizeSort
case "time":
gLocalOpts.sortMethods[path] = timeSort
case "ctime":
gLocalOpts.sortMethods[path] = ctimeSort
case "atime":
gLocalOpts.sortMethods[path] = atimeSort
case "ext":
gLocalOpts.sortMethods[path] = extSort
default:
app.ui.echoerr("sortby: value should either be 'natural', 'name', 'size', 'time', 'atime', 'ctime' or 'ext'")
return
}
app.nav.sort()
app.ui.sort()
default:
app.ui.echoerrf("unknown option: %s", e.opt)
return
}
app.ui.loadFileInfo(app.nav)
}

func (e *mapExpr) eval(app *app, args []string) {
if e.expr == nil {
delete(gOpts.keys, e.keys)
Expand Down
78 changes: 78 additions & 0 deletions eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,84 @@ var gEvalTests = []struct {
[]expr{&setExpr{"ratios", "1:2:3"}, &setExpr{"hidden", ""}},
},

{
"setlocal /foo/bar hidden # trailing comments are allowed",
[]string{"setlocal", "/foo/bar", "hidden", "\n"},
[]expr{&setLocalExpr{"/foo/bar", "hidden", ""}},
},

{
"setlocal /foo/bar hidden; setlocal /foo/bar reverse",
[]string{"setlocal", "/foo/bar", "hidden", ";", "setlocal", "/foo/bar", "reverse", "\n"},
[]expr{&setLocalExpr{"/foo/bar", "hidden", ""}, &setLocalExpr{"/foo/bar", "reverse", ""}},
},

{
"setlocal /foo/bar hidden\nsetlocal /foo/bar reverse",
[]string{"setlocal", "/foo/bar", "hidden", "\n", "setlocal", "/foo/bar", "reverse", "\n"},
[]expr{&setLocalExpr{"/foo/bar", "hidden", ""}, &setLocalExpr{"/foo/bar", "reverse", ""}},
},

{
`setlocal /foo/bar info ""`,
[]string{"setlocal", "/foo/bar", "info", "", "\n"},
[]expr{&setLocalExpr{"/foo/bar", "info", ""}},
},

{
`setlocal /foo/bar info "size"`,
[]string{"setlocal", "/foo/bar", "info", "size", "\n"},
[]expr{&setLocalExpr{"/foo/bar", "info", "size"}},
},

{
"setlocal /foo/bar info size:time",
[]string{"setlocal", "/foo/bar", "info", "size:time", "\n"},
[]expr{&setLocalExpr{"/foo/bar", "info", "size:time"}},
},

{
"setlocal /foo/bar info size:time;",
[]string{"setlocal", "/foo/bar", "info", "size:time", ";"},
[]expr{&setLocalExpr{"/foo/bar", "info", "size:time"}},
},

{
":setlocal /foo/bar info size:time",
[]string{":", "setlocal", "/foo/bar", "info", "size:time", "\n", "\n"},
[]expr{&listExpr{[]expr{&setLocalExpr{"/foo/bar", "info", "size:time"}}, 1}},
},

{
":setlocal /foo/bar info size:time\nsetlocal /foo/bar hidden",
[]string{":", "setlocal", "/foo/bar", "info", "size:time", "\n", "\n", "setlocal", "/foo/bar", "hidden", "\n"},
[]expr{&listExpr{[]expr{&setLocalExpr{"/foo/bar", "info", "size:time"}}, 1}, &setLocalExpr{"/foo/bar", "hidden", ""}},
},

{
":setlocal /foo/bar info size:time;",
[]string{":", "setlocal", "/foo/bar", "info", "size:time", ";", "\n"},
[]expr{&listExpr{[]expr{&setLocalExpr{"/foo/bar", "info", "size:time"}}, 1}},
},

{
":setlocal /foo/bar info size:time;\nsetlocal /foo/bar hidden",
[]string{":", "setlocal", "/foo/bar", "info", "size:time", ";", "\n", "setlocal", "/foo/bar", "hidden", "\n"},
[]expr{&listExpr{[]expr{&setLocalExpr{"/foo/bar", "info", "size:time"}}, 1}, &setLocalExpr{"/foo/bar", "hidden", ""}},
},

{
"setlocal /foo/bar info size:time\n setlocal /foo/bar hidden",
[]string{"setlocal", "/foo/bar", "info", "size:time", "\n", "setlocal", "/foo/bar", "hidden", "\n"},
[]expr{&setLocalExpr{"/foo/bar", "info", "size:time"}, &setLocalExpr{"/foo/bar", "hidden", ""}},
},

{
"setlocal /foo/bar info size:time \nsetlocal /foo/bar hidden",
[]string{"setlocal", "/foo/bar", "info", "size:time", "\n", "setlocal", "/foo/bar", "hidden", "\n"},
[]expr{&setLocalExpr{"/foo/bar", "info", "size:time"}, &setLocalExpr{"/foo/bar", "hidden", ""}},
},

{
"map gh cd ~",
[]string{"map", "gh", "cd", "~", "\n"},
Expand Down
Loading