diff --git a/0.21.0-release-notes.md b/0.21.0-release-notes.md index 0dc8a55a8..95de20b19 100644 --- a/0.21.0-release-notes.md +++ b/0.21.0-release-notes.md @@ -14,3 +14,24 @@ - The commands `!=`, `!=s` and `not-eq` now only accepts two arguments ([#1767](https://b.elv.sh/1767)). + +- The commands `edit:kill-left-alnum-word` and `edit:kill-right-alnum-word` + have been renamed to `edit:kill-alnum-word-left` and + `edit:kill-alnum-word-right`, to be consistent with the documentation and + the names of other similar commands. + + If you need to write code that supports both names, use `has-key` to detect + which name is available: + + ```elvish + fn kill-alnum-word-left { + if (has-key edit: kill-alnum-word-left~) { + edit:kill-alnum-word-left + } else { + edit:kill-left-alnum-word + } + } + ``` + +- The implicit cd feature has been removed and replaced by the ability to type + a literal path in location mode ([#1099](https://b.elv.sh/1099)). diff --git a/go.sum b/go.sum index cb466e6ec..6d860a729 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= @@ -8,9 +9,11 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sourcegraph/jsonrpc2 v0.2.0 h1:KjN/dC4fP6aN9030MZCJs9WQbTOjWHhrtKVpzzSrr/U= github.com/sourcegraph/jsonrpc2 v0.2.0/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= @@ -19,5 +22,6 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= pkg.nimblebun.works/go-lsp v1.1.0 h1:TH5ro4p2vlDtELK4LoVeKs4TsKm6aW1f5WP8jHm/9m4= pkg.nimblebun.works/go-lsp v1.1.0/go.mod h1:Suh759Ki+DjU0zwf0xkl1H6Ln1C6/+GtYyNofbtfcug= diff --git a/pkg/cli/modes/location.go b/pkg/cli/modes/location.go index be1b9b52b..69dce33d5 100644 --- a/pkg/cli/modes/location.go +++ b/pkg/cli/modes/location.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math" + "os" "path/filepath" "regexp" "strings" @@ -111,7 +112,7 @@ func NewLocation(app cli.App, cfg LocationSpec) (Location, error) { }, }, OnFilter: func(w tk.ComboBox, p string) { - w.ListBox().Reset(l.filter(cfg.Filter.makePredicate(p)), 0) + w.ListBox().Reset(l.filter(p, cfg.Filter.makePredicate(p)), 0) }, }) return w, nil @@ -154,8 +155,16 @@ type locationList struct { dirs []storedefs.Dir } -func (l locationList) filter(p func(string) bool) locationList { +func (l locationList) filter(path string, p func(string) bool) locationList { var filteredDirs []storedefs.Dir + // The `path` argument is the raw filter string. If it is a valid directory + // it is pinned to the top of the resulting filtered location history with a + // score that marks it as a special-case. This allows a user to quickly + // select a literal directory such as /tmp using location mode rather than + // the `cd` command. + if fi, err := os.Stat(path); err == nil && fi.Mode().IsDir() { + filteredDirs = append(filteredDirs, storedefs.Dir{Path: path, Score: -1}) + } for _, dir := range l.dirs { if p(fsutil.TildeAbbr(dir.Path)) { filteredDirs = append(filteredDirs, dir) diff --git a/pkg/cli/modes/location_test.go b/pkg/cli/modes/location_test.go index 0e68643cd..a4a3041dc 100644 --- a/pkg/cli/modes/location_test.go +++ b/pkg/cli/modes/location_test.go @@ -120,6 +120,31 @@ func TestLocation_FullWorkflow(t *testing.T) { } } +// Test filtering that includes a literal path to a directory that exists. +func TestLocation_LiteralDir(t *testing.T) { + home := testutil.InTempHome(t) + f := Setup() + defer f.Stop() + + dirs := []storedefs.Dir{ + {Path: home, Score: 100}, + {Path: fixPath("/tmp/foo/bar/lorem/ipsum"), Score: 50}, + } + startLocation(f.App, LocationSpec{Store: locationStore{ + storedDirs: dirs, + chdir: func(dir string) error { return nil }, + }}) + + wantBuf := locationBuf( + fixPath("/"), + " -1 "+fixPath("/"), + " 50 "+fixPath("/tmp/foo/bar/lorem/ipsum")) + for _, c := range fixPath("/") { + f.TTY.Inject(term.K(c)) + } + f.TTY.TestBuffer(t, wantBuf) +} + func TestLocation_Hidden(t *testing.T) { f := Setup() defer f.Stop() diff --git a/pkg/edit/buffer_builtins.go b/pkg/edit/buffer_builtins.go index 875f10027..ea738a1c3 100644 --- a/pkg/edit/buffer_builtins.go +++ b/pkg/edit/buffer_builtins.go @@ -51,8 +51,8 @@ var bufferBuiltinsData = map[string]func(*tk.CodeBuffer){ "kill-word-right": makeKill(moveDotRightWord), "kill-small-word-left": makeKill(moveDotLeftSmallWord), "kill-small-word-right": makeKill(moveDotRightSmallWord), - "kill-left-alnum-word": makeKill(moveDotLeftAlnumWord), - "kill-right-alnum-word": makeKill(moveDotRightAlnumWord), + "kill-alnum-word-left": makeKill(moveDotLeftAlnumWord), + "kill-alnum-word-right": makeKill(moveDotRightAlnumWord), "kill-line-left": makeKill(moveDotSOL), "kill-line-right": makeKill(moveDotEOL), diff --git a/pkg/eval/builtin_fn_fs.d.elv b/pkg/eval/builtin_fn_fs.d.elv index 8549fa34c..74c040702 100644 --- a/pkg/eval/builtin_fn_fs.d.elv +++ b/pkg/eval/builtin_fn_fs.d.elv @@ -1,12 +1,15 @@ # Changes directory. # # This affects the entire process, including parallel tasks that are started -# implicitly (such as prompt functions) or explicitly (such as one started by +# implicitly (such as prompt functions) or explicitly (such as started by # [`peach`]()). # -# Note that Elvish's `cd` does not support `cd -`. +# Note that Elvish's `cd` does not support `cd -`. You can also change to a +# specific directory by starting [Location +# Mode](../learn/tour.html#location-mode) and typing the desired path as a +# filter. # -# See also [`$pwd`](). +# See also [`$pwd`]() and [Location Mode](../learn/tour.html#location-mode). fn cd {|dirname| } # If `$path` represents a path under the home directory, replace the home diff --git a/pkg/eval/builtin_fn_num.d.elv b/pkg/eval/builtin_fn_num.d.elv index 6a9f0e7eb..a408a0f18 100644 --- a/pkg/eval/builtin_fn_num.d.elv +++ b/pkg/eval/builtin_fn_num.d.elv @@ -300,10 +300,6 @@ fn * {|@num| } # ~> / 2 0.0 # ▶ (num +Inf) # ``` -# -# When given no argument, this command is equivalent to `cd /`, due to the -# implicit cd feature. (The implicit cd feature will probably change to avoid -# this oddity). #doc:id div fn / {|x-num @y-num| } diff --git a/pkg/eval/builtin_fn_num.go b/pkg/eval/builtin_fn_num.go index de831badd..036dca766 100644 --- a/pkg/eval/builtin_fn_num.go +++ b/pkg/eval/builtin_fn_num.go @@ -258,12 +258,8 @@ func mul(rawNums ...vals.Num) vals.Num { } } +// Implement numerical division. func slash(fm *Frame, args ...vals.Num) error { - if len(args) == 0 { - // cd / - return fm.Evaler.Chdir("/") - } - // Division result, err := div(args...) if err != nil { return err diff --git a/pkg/eval/external_cmd.go b/pkg/eval/external_cmd.go index de3f2e753..5c32fc640 100644 --- a/pkg/eval/external_cmd.go +++ b/pkg/eval/external_cmd.go @@ -12,7 +12,6 @@ import ( "src.elv.sh/pkg/eval/errs" "src.elv.sh/pkg/eval/vals" - "src.elv.sh/pkg/fsutil" "src.elv.sh/pkg/parse" "src.elv.sh/pkg/persistent/hash" ) @@ -23,8 +22,6 @@ var ( // // TODO: Catch this kind of errors at compilation time. ErrExternalCmdOpts = errors.New("external commands don't accept elvish options") - // ErrImplicitCdNoArg is thrown when an implicit cd form is passed arguments. - ErrImplicitCdNoArg = errors.New("implicit cd accepts no arguments") ) // externalCmd is an external command. @@ -61,16 +58,6 @@ func (e externalCmd) Call(fm *Frame, argVals []any, opts map[string]any) error { if len(opts) > 0 { return ErrExternalCmdOpts } - if fsutil.DontSearch(e.Name) { - stat, err := os.Stat(e.Name) - if err == nil && stat.IsDir() { - // implicit cd - if len(argVals) > 0 { - return ErrImplicitCdNoArg - } - return fm.Evaler.Chdir(e.Name) - } - } files := make([]*os.File, len(fm.ports)) for i, port := range fm.ports { diff --git a/pkg/fsutil/search.go b/pkg/fsutil/search.go index 8efd47715..da9a458d0 100644 --- a/pkg/fsutil/search.go +++ b/pkg/fsutil/search.go @@ -11,7 +11,9 @@ import ( // DontSearch determines whether the path to an external command should be // taken literally and not searched. func DontSearch(exe string) bool { - return exe == ".." || strings.ContainsRune(exe, filepath.Separator) || + return exe == "." || + exe == ".." || + strings.ContainsRune(exe, filepath.Separator) || strings.ContainsRune(exe, '/') } diff --git a/website/learn/tour.md b/website/learn/tour.md index 0c9effa84..b5dfce6d2 100644 --- a/website/learn/tour.md +++ b/website/learn/tour.md @@ -943,7 +943,7 @@ pressing . For example, to walk through commands starting with @ttyshot learn/tour/history-walk-prefix -### History listing +### History mode Press Ctrl-R to list the full command history: @@ -960,12 +960,14 @@ last command. Press Alt-, to trigger it: @ttyshot learn/tour/lastcmd -## Directory history +## Location mode Elvish remembers which directories you have visited. Press Ctrl-L to list visited directories. Use and to navigate the list, Enter to change to that directory, or Escape to -cancel. +cancel. If you filter the results using a path that matches an existing +directory it will appear at the top of the directories in your history with a +special score of -1 even if that directory is not in your visited history. @ttyshot learn/tour/location