Skip to content

Commit 83dfec0

Browse files
authored
Merge pull request #170 from ilyagr/fuzzy-zb
Allow `z -b` to do fuzzy matching
2 parents 3009e33 + 3703c5a commit 83dfec0

File tree

2 files changed

+105
-78
lines changed

2 files changed

+105
-78
lines changed

README.md

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,17 @@ From people using z.lua:
4242
## Examples
4343

4444
```bash
45-
z foo # cd to most frecent dir matching foo
46-
z foo bar # cd to most frecent dir matching foo and bar
47-
z -r foo # cd to the highest ranked dir matching foo
48-
z -t foo # cd to most recently accessed dir matching foo
49-
z -l foo # list matches instead of cd
50-
z -c foo # restrict matches to subdirs of $PWD
51-
z -e foo # echo the best match, don't cd
52-
z -i foo # cd with interactive selection
53-
z -I foo # cd with interactive selection using fzf
54-
z -b foo # cd to the parent directory starting with foo
45+
z foo # cd to most frecent dir matching foo
46+
z foo bar # cd to most frecent dir matching foo and bar
47+
z -r foo # cd to the highest ranked dir matching foo
48+
z -t foo # cd to most recently accessed dir matching foo
49+
z -l foo # list matches instead of cd
50+
z -c foo # restrict matches to subdirs of $PWD
51+
z -e foo # echo the best match, don't cd
52+
z -i foo # cd with interactive selection
53+
z -I foo # cd with interactive selection using fzf
54+
z -b foo # cd to the parent directory starting with foo
55+
z -b foo bar # replace foo with bar in cwd and cd there
5556
```
5657

5758

@@ -176,16 +177,12 @@ To z.lua, a directory that has low ranking but has been accessed recently will q
176177

177178
## Default Matching
178179

179-
By default, z.lua uses default matching algorithm similar to the original z.sh. Paths must be match all of the regexes in order.
180+
By default, `z.lua` uses default matching algorithm similar to the original `z.sh`. Paths must be match all of the regexes in order.
180181

181182
- cd to a directory contains foo:
182183

183184
z foo
184185

185-
- cd to a directory ends with foo:
186-
187-
z foo$
188-
189186
- use multiple arguments:
190187

191188
Assuming the following database:
@@ -195,6 +192,15 @@ By default, z.lua uses default matching algorithm similar to the original z.sh.
195192

196193
`"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`.
197194

195+
- use regexes:
196+
197+
```bash
198+
z foo$ # cd to a directory ends with foo
199+
z %d # cd to a directory that contains a digit
200+
```
201+
202+
Unlike `z.sh`, `z.lua` uses the [Lua regular expression syntax](https://www.lua.org/pil/20.2.html).
203+
198204
## Enhanced Matching
199205

200206
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
319325
- **(No argument)**: `cd` into the project root, the project root the nearest parent directory with `.git`/`.hg`/`.svn` in it.
320326
- **(One argument)**: `cd` into the closest parent starting with keyword, if not find, go to the parent containing keyword.
321327
- **(Two arguments)**: replace the first value with the second one (in the current path).
328+
If simple substitution does not work, falls back to fuzzily replacing path components.
322329

323330
Let's start by aliasing `z -b` to `zb`:
324331

@@ -338,6 +345,11 @@ Let's start by aliasing `z -b` to `zb`:
338345
# substitute jekyll with ghost
339346
~/github/jekyll/test$ zb jekyll ghost
340347
=> cd ~/github/ghost/test
348+
349+
# same as above, but fuzzy
350+
~/github/jekyll/test$ zb jek gh
351+
=> z ~/github/ gh /test
352+
=> cd ~/github/ghost/test # Assuming that's the most frecent match
341353
```
342354

343355
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` ``).

z.lua

Lines changed: 78 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,18 @@
1212
-- * compatible with lua 5.1, 5.2 and 5.3+
1313
--
1414
-- USE:
15-
-- * z foo # cd to most frecent dir matching foo
16-
-- * z foo bar # cd to most frecent dir matching foo and bar
17-
-- * z -r foo # cd to highest ranked dir matching foo
18-
-- * z -t foo # cd to most recently accessed dir matching foo
19-
-- * z -l foo # list matches instead of cd
20-
-- * z -c foo # restrict matches to subdirs of $PWD
21-
-- * z -e foo # echo the best match, don't cd
22-
-- * z -x path # remove path from history
23-
-- * z -i foo # cd with interactive selection
24-
-- * z -I foo # cd with interactive selection using fzf
25-
-- * z -b foo # cd to the parent directory starting with foo
15+
-- * z foo # cd to most frecent dir matching foo
16+
-- * z foo bar # cd to most frecent dir matching foo and bar
17+
-- * z -r foo # cd to highest ranked dir matching foo
18+
-- * z -t foo # cd to most recently accessed dir matching foo
19+
-- * z -l foo # list matches instead of cd
20+
-- * z -c foo # restrict matches to subdirs of $PWD
21+
-- * z -e foo # echo the best match, don't cd
22+
-- * z -x path # remove path from history
23+
-- * z -i foo # cd with interactive selection
24+
-- * z -I foo # cd with interactive selection using fzf
25+
-- * z -b foo # cd to the parent directory starting with foo
26+
-- * z -b foo bar # replace foo with bar in cwd and cd there
2627
--
2728
-- Bash Install:
2829
-- * put something like this in your .bashrc:
@@ -46,7 +47,7 @@
4647
--
4748
-- Fish Shell Install:
4849
-- * put something like this in your config file:
49-
-- source (lua /path/to/z.lua --init fish | psub)
50+
-- lua /path/to/z.lua --init fish | source
5051
--
5152
-- Power Shell Install:
5253
-- * put something like this in your config file:
@@ -1719,47 +1720,45 @@ function cd_backward(args, options, pwd)
17191720
local pwd = (pwd ~= nil) and pwd or os.pwd()
17201721
if nargs == 0 then
17211722
return find_vcs_root(pwd)
1722-
elseif nargs == 1 then
1723-
if args[1]:sub(1, 2) == '..' then
1724-
local size = args[1]:len() - 1
1725-
if args[1]:match('^%.%.+$') then
1726-
size = args[1]:len() - 1
1727-
elseif args[1]:match('^%.%.%d+$') then
1728-
size = tonumber(args[1]:sub(3))
1729-
else
1730-
return nil
1731-
end
1732-
local path = pwd
1733-
for index = 1, size do
1734-
path = os.path.join(path, '..')
1735-
end
1736-
return os.path.normpath(path)
1723+
elseif nargs == 1 and args[1]:sub(1, 2) == '..' then
1724+
local size = args[1]:len() - 1
1725+
if args[1]:match('^%.%.+$') then
1726+
size = args[1]:len() - 1
1727+
elseif args[1]:match('^%.%.%d+$') then
1728+
size = tonumber(args[1]:sub(3))
17371729
else
1738-
pwd = os.path.split(pwd)
1739-
local test = windows and pwd:gsub('\\', '/') or pwd
1740-
local key = windows and args[1]:lower() or args[1]
1741-
if not key:match('%u') then
1742-
test = test:lower()
1743-
end
1744-
local pos, ends = test:rfind('/' .. key)
1745-
if pos then
1746-
ends = test:find('/', pos + key:len() + 1, true)
1747-
ends = ends and ends or test:len()
1748-
return os.path.normpath(pwd:sub(1, ends))
1749-
elseif windows and test:startswith(key) then
1750-
ends = test:find('/', key:len(), true)
1751-
ends = ends and ends or test:len()
1752-
return os.path.normpath(pwd:sub(1, ends))
1753-
end
1754-
pos = test:rfind(key)
1755-
if pos then
1756-
ends = test:find('/', pos + key:len(), true)
1757-
ends = ends and ends or test:len()
1758-
return os.path.normpath(pwd:sub(1, ends))
1759-
end
17601730
return nil
17611731
end
1762-
else
1732+
local path = pwd
1733+
for index = 1, size do
1734+
path = os.path.join(path, '..')
1735+
end
1736+
return os.path.normpath(path)
1737+
elseif nargs == 1 then
1738+
pwd = os.path.split(pwd)
1739+
local test = windows and pwd:gsub('\\', '/') or pwd
1740+
local key = windows and args[1]:lower() or args[1]
1741+
if not key:match('%u') then
1742+
test = test:lower()
1743+
end
1744+
local pos, ends = test:rfind('/' .. key)
1745+
if pos then
1746+
ends = test:find('/', pos + key:len() + 1, true)
1747+
ends = ends and ends or test:len()
1748+
return os.path.normpath(pwd:sub(1, ends))
1749+
elseif windows and test:startswith(key) then
1750+
ends = test:find('/', key:len(), true)
1751+
ends = ends and ends or test:len()
1752+
return os.path.normpath(pwd:sub(1, ends))
1753+
end
1754+
pos = test:rfind(key)
1755+
if pos then
1756+
ends = test:find('/', pos + key:len(), true)
1757+
ends = ends and ends or test:len()
1758+
return os.path.normpath(pwd:sub(1, ends))
1759+
end
1760+
return nil
1761+
elseif nargs == 2 then
17631762
local test = windows and pwd:gsub('\\', '/') or pwd
17641763
local src = args[1]
17651764
local dst = args[2]
@@ -1770,10 +1769,25 @@ function cd_backward(args, options, pwd)
17701769
if not start then
17711770
return pwd
17721771
end
1772+
17731773
local lhs = pwd:sub(1, start - 1)
17741774
local rhs = pwd:sub(ends + 1)
1775-
return lhs .. dst .. rhs
1775+
local newpath = lhs .. dst .. rhs
1776+
if os.path.isdir(newpath) then
1777+
return newpath
1778+
end
1779+
1780+
-- Get rid of the entire path component that matched `src`.
1781+
lhs = lhs:gsub("[^/]*$", "")
1782+
rhs = rhs:gsub("^[^/]*", "")
1783+
return z_cd({lhs, dst, rhs})
1784+
-- In the future, it would make sense to have `z -b -c from to to2`
1785+
-- to z_cd({lhs, dst[1], dst[2]}). Without `-c`, we probably still
1786+
-- want to support only 2 argumets.
17761787
end
1788+
1789+
io.stderr:write("Error: " .. Z_CMD .. " -b takes at most 2 arguments.\n")
1790+
return nil
17771791
end
17781792

17791793

@@ -1922,7 +1936,7 @@ function main(argv)
19221936
if options['--cd'] or options['-e'] then
19231937
local path = ''
19241938
if options['-b'] then
1925-
if Z_INTERACTIVE == 0 then
1939+
if #args > 0 or Z_INTERACTIVE == 0 then
19261940
path = cd_backward(args, options)
19271941
else
19281942
path = cd_breadcrumbs('', Z_INTERACTIVE)
@@ -2701,17 +2715,18 @@ end
27012715
-----------------------------------------------------------------------
27022716
function z_help()
27032717
local cmd = Z_CMD .. ' '
2704-
print(cmd .. 'foo # cd to most frecent dir matching foo')
2705-
print(cmd .. 'foo bar # cd to most frecent dir matching foo and bar')
2706-
print(cmd .. '-r foo # cd to highest ranked dir matching foo')
2707-
print(cmd .. '-t foo # cd to most recently accessed dir matching foo')
2708-
print(cmd .. '-l foo # list matches instead of cd')
2709-
print(cmd .. '-c foo # restrict matches to subdirs of $PWD')
2710-
print(cmd .. '-e foo # echo the best match, don\'t cd')
2711-
print(cmd .. '-x path # remove path from history')
2712-
print(cmd .. '-i foo # cd with interactive selection')
2713-
print(cmd .. '-I foo # cd with interactive selection using fzf')
2714-
print(cmd .. '-b foo # cd to the parent directory starting with foo')
2718+
print(cmd .. 'foo # cd to most frecent dir matching foo')
2719+
print(cmd .. 'foo bar # cd to most frecent dir matching foo and bar')
2720+
print(cmd .. '-r foo # cd to highest ranked dir matching foo')
2721+
print(cmd .. '-t foo # cd to most recently accessed dir matching foo')
2722+
print(cmd .. '-l foo # list matches instead of cd')
2723+
print(cmd .. '-c foo # restrict matches to subdirs of $PWD')
2724+
print(cmd .. '-e foo # echo the best match, don\'t cd')
2725+
print(cmd .. '-x path # remove path from history')
2726+
print(cmd .. '-i foo # cd with interactive selection')
2727+
print(cmd .. '-I foo # cd with interactive selection using fzf')
2728+
print(cmd .. '-b foo # cd to the parent directory starting with foo')
2729+
print(cmd .. '-b foo bar # replace foo with bar in cwd and cd there')
27152730
end
27162731

27172732

0 commit comments

Comments
 (0)