1
0
mirror of https://github.com/skywind3000/z.lua synced 2026-03-15 12:19:49 +00:00

Merge pull request #170 from ilyagr/fuzzy-zb

Allow `z -b` to do fuzzy matching
This commit is contained in:
Linwei
2022-09-06 21:13:00 +08:00
committed by GitHub
2 changed files with 105 additions and 78 deletions

View File

@@ -42,16 +42,17 @@ From people using z.lua:
## Examples
```bash
z foo # cd to most frecent dir matching foo
z foo bar # cd to most frecent dir matching foo and bar
z -r foo # cd to the highest ranked dir matching foo
z -t foo # cd to most recently accessed dir matching foo
z -l foo # list matches instead of cd
z -c foo # restrict matches to subdirs of $PWD
z -e foo # echo the best match, don't cd
z -i foo # cd with interactive selection
z -I foo # cd with interactive selection using fzf
z -b foo # cd to the parent directory starting with foo
z foo # cd to most frecent dir matching foo
z foo bar # cd to most frecent dir matching foo and bar
z -r foo # cd to the highest ranked dir matching foo
z -t foo # cd to most recently accessed dir matching foo
z -l foo # list matches instead of cd
z -c foo # restrict matches to subdirs of $PWD
z -e foo # echo the best match, don't cd
z -i foo # cd with interactive selection
z -I foo # cd with interactive selection using fzf
z -b foo # cd to the parent directory starting with foo
z -b foo bar # replace foo with bar in cwd and cd there
```
@@ -176,16 +177,12 @@ To z.lua, a directory that has low ranking but has been accessed recently will q
## Default Matching
By default, z.lua uses default matching algorithm similar to the original z.sh. Paths must be match all of the regexes in order.
By default, `z.lua` uses default matching algorithm similar to the original `z.sh`. Paths must be match all of the regexes in order.
- cd to a directory contains foo:
z foo
- cd to a directory ends with foo:
z foo$
- use multiple arguments:
Assuming the following database:
@@ -195,6 +192,15 @@ By default, z.lua uses default matching algorithm similar to the original z.sh.
`"z in"` would cd into `/home/user/mail/inbox` as the higher weighted entry. However you can pass multiple arguments to z.lua to prefer a different entry. In the above example, `"z w in"` would then change directory to `/home/user/work/inbox`.
- use regexes:
```bash
z foo$ # cd to a directory ends with foo
z %d # cd to a directory that contains a digit
```
Unlike `z.sh`, `z.lua` uses the [Lua regular expression syntax](https://www.lua.org/pil/20.2.html).
## Enhanced Matching
Enhanced matching can be enabled by exporting the environment:
@@ -319,6 +325,7 @@ New option `"-b"` can quickly go back to a specific parent directory in bash ins
- **(No argument)**: `cd` into the project root, the project root the nearest parent directory with `.git`/`.hg`/`.svn` in it.
- **(One argument)**: `cd` into the closest parent starting with keyword, if not find, go to the parent containing keyword.
- **(Two arguments)**: replace the first value with the second one (in the current path).
If simple substitution does not work, falls back to fuzzily replacing path components.
Let's start by aliasing `z -b` to `zb`:
@@ -338,6 +345,11 @@ Let's start by aliasing `z -b` to `zb`:
# substitute jekyll with ghost
~/github/jekyll/test$ zb jekyll ghost
=> cd ~/github/ghost/test
# same as above, but fuzzy
~/github/jekyll/test$ zb jek gh
=> z ~/github/ gh /test
=> cd ~/github/ghost/test # Assuming that's the most frecent match
```
Backward jumping can also be used with `$_ZL_ECHO` option (echo $PWD after cd), which makes it possible to combine them with other tools without actually changing the working directory (eg. ``ls `zb git` ``).

141
z.lua
View File

@@ -12,17 +12,18 @@
-- * compatible with lua 5.1, 5.2 and 5.3+
--
-- USE:
-- * z foo # cd to most frecent dir matching foo
-- * z foo bar # cd to most frecent dir matching foo and bar
-- * z -r foo # cd to highest ranked dir matching foo
-- * z -t foo # cd to most recently accessed dir matching foo
-- * z -l foo # list matches instead of cd
-- * z -c foo # restrict matches to subdirs of $PWD
-- * z -e foo # echo the best match, don't cd
-- * z -x path # remove path from history
-- * z -i foo # cd with interactive selection
-- * z -I foo # cd with interactive selection using fzf
-- * z -b foo # cd to the parent directory starting with foo
-- * z foo # cd to most frecent dir matching foo
-- * z foo bar # cd to most frecent dir matching foo and bar
-- * z -r foo # cd to highest ranked dir matching foo
-- * z -t foo # cd to most recently accessed dir matching foo
-- * z -l foo # list matches instead of cd
-- * z -c foo # restrict matches to subdirs of $PWD
-- * z -e foo # echo the best match, don't cd
-- * z -x path # remove path from history
-- * z -i foo # cd with interactive selection
-- * z -I foo # cd with interactive selection using fzf
-- * z -b foo # cd to the parent directory starting with foo
-- * z -b foo bar # replace foo with bar in cwd and cd there
--
-- Bash Install:
-- * put something like this in your .bashrc:
@@ -46,7 +47,7 @@
--
-- Fish Shell Install:
-- * put something like this in your config file:
-- source (lua /path/to/z.lua --init fish | psub)
-- lua /path/to/z.lua --init fish | source
--
-- Power Shell Install:
-- * put something like this in your config file:
@@ -1719,47 +1720,45 @@ function cd_backward(args, options, pwd)
local pwd = (pwd ~= nil) and pwd or os.pwd()
if nargs == 0 then
return find_vcs_root(pwd)
elseif nargs == 1 then
if args[1]:sub(1, 2) == '..' then
local size = args[1]:len() - 1
if args[1]:match('^%.%.+$') then
size = args[1]:len() - 1
elseif args[1]:match('^%.%.%d+$') then
size = tonumber(args[1]:sub(3))
else
return nil
end
local path = pwd
for index = 1, size do
path = os.path.join(path, '..')
end
return os.path.normpath(path)
elseif nargs == 1 and args[1]:sub(1, 2) == '..' then
local size = args[1]:len() - 1
if args[1]:match('^%.%.+$') then
size = args[1]:len() - 1
elseif args[1]:match('^%.%.%d+$') then
size = tonumber(args[1]:sub(3))
else
pwd = os.path.split(pwd)
local test = windows and pwd:gsub('\\', '/') or pwd
local key = windows and args[1]:lower() or args[1]
if not key:match('%u') then
test = test:lower()
end
local pos, ends = test:rfind('/' .. key)
if pos then
ends = test:find('/', pos + key:len() + 1, true)
ends = ends and ends or test:len()
return os.path.normpath(pwd:sub(1, ends))
elseif windows and test:startswith(key) then
ends = test:find('/', key:len(), true)
ends = ends and ends or test:len()
return os.path.normpath(pwd:sub(1, ends))
end
pos = test:rfind(key)
if pos then
ends = test:find('/', pos + key:len(), true)
ends = ends and ends or test:len()
return os.path.normpath(pwd:sub(1, ends))
end
return nil
end
else
local path = pwd
for index = 1, size do
path = os.path.join(path, '..')
end
return os.path.normpath(path)
elseif nargs == 1 then
pwd = os.path.split(pwd)
local test = windows and pwd:gsub('\\', '/') or pwd
local key = windows and args[1]:lower() or args[1]
if not key:match('%u') then
test = test:lower()
end
local pos, ends = test:rfind('/' .. key)
if pos then
ends = test:find('/', pos + key:len() + 1, true)
ends = ends and ends or test:len()
return os.path.normpath(pwd:sub(1, ends))
elseif windows and test:startswith(key) then
ends = test:find('/', key:len(), true)
ends = ends and ends or test:len()
return os.path.normpath(pwd:sub(1, ends))
end
pos = test:rfind(key)
if pos then
ends = test:find('/', pos + key:len(), true)
ends = ends and ends or test:len()
return os.path.normpath(pwd:sub(1, ends))
end
return nil
elseif nargs == 2 then
local test = windows and pwd:gsub('\\', '/') or pwd
local src = args[1]
local dst = args[2]
@@ -1770,10 +1769,25 @@ function cd_backward(args, options, pwd)
if not start then
return pwd
end
local lhs = pwd:sub(1, start - 1)
local rhs = pwd:sub(ends + 1)
return lhs .. dst .. rhs
local newpath = lhs .. dst .. rhs
if os.path.isdir(newpath) then
return newpath
end
-- Get rid of the entire path component that matched `src`.
lhs = lhs:gsub("[^/]*$", "")
rhs = rhs:gsub("^[^/]*", "")
return z_cd({lhs, dst, rhs})
-- In the future, it would make sense to have `z -b -c from to to2`
-- to z_cd({lhs, dst[1], dst[2]}). Without `-c`, we probably still
-- want to support only 2 argumets.
end
io.stderr:write("Error: " .. Z_CMD .. " -b takes at most 2 arguments.\n")
return nil
end
@@ -1922,7 +1936,7 @@ function main(argv)
if options['--cd'] or options['-e'] then
local path = ''
if options['-b'] then
if Z_INTERACTIVE == 0 then
if #args > 0 or Z_INTERACTIVE == 0 then
path = cd_backward(args, options)
else
path = cd_breadcrumbs('', Z_INTERACTIVE)
@@ -2701,17 +2715,18 @@ end
-----------------------------------------------------------------------
function z_help()
local cmd = Z_CMD .. ' '
print(cmd .. 'foo # cd to most frecent dir matching foo')
print(cmd .. 'foo bar # cd to most frecent dir matching foo and bar')
print(cmd .. '-r foo # cd to highest ranked dir matching foo')
print(cmd .. '-t foo # cd to most recently accessed dir matching foo')
print(cmd .. '-l foo # list matches instead of cd')
print(cmd .. '-c foo # restrict matches to subdirs of $PWD')
print(cmd .. '-e foo # echo the best match, don\'t cd')
print(cmd .. '-x path # remove path from history')
print(cmd .. '-i foo # cd with interactive selection')
print(cmd .. '-I foo # cd with interactive selection using fzf')
print(cmd .. '-b foo # cd to the parent directory starting with foo')
print(cmd .. 'foo # cd to most frecent dir matching foo')
print(cmd .. 'foo bar # cd to most frecent dir matching foo and bar')
print(cmd .. '-r foo # cd to highest ranked dir matching foo')
print(cmd .. '-t foo # cd to most recently accessed dir matching foo')
print(cmd .. '-l foo # list matches instead of cd')
print(cmd .. '-c foo # restrict matches to subdirs of $PWD')
print(cmd .. '-e foo # echo the best match, don\'t cd')
print(cmd .. '-x path # remove path from history')
print(cmd .. '-i foo # cd with interactive selection')
print(cmd .. '-I foo # cd with interactive selection using fzf')
print(cmd .. '-b foo # cd to the parent directory starting with foo')
print(cmd .. '-b foo bar # replace foo with bar in cwd and cd there')
end