1
0
mirror of https://github.com/skywind3000/z.lua synced 2026-06-29 17:38:51 +00:00
This commit is contained in:
manhong2112
2019-02-04 02:03:28 +08:00
5 changed files with 641 additions and 42 deletions
+63 -1
View File
@@ -20,7 +20,8 @@ z.lua 是一个快速路径切换工具,它会跟踪你在 shell 下访问过
- 新增:环境变量 "$_ZL_ADD_ONCE" 设成 1 的话性仅当前路径改变时才更新数据库。
- 新增:增强匹配模式,将环境变量 "$_ZL_MATCH_MODE" 设置成 1 可以启用。
- 新增:交互选择模式,如果有多个匹配结果的话,跳转前允许你进行选择。
- 新增:支持 fzf 来进行多结果筛选,见后面文档。
- 新增:支持 fzf 来进行多结果筛选,见后面文档。
- 新增:快速跳转到父目录,代替反复 “cd ../../.." 。
## Examples
@@ -248,6 +249,66 @@ PS:如果你使用 Fish shell,需要 2.7.0 以上才支持该功能。
PS:你可以使用 `$_ZL_FZF` 环境变量来精确指明 fzf 的可执行路径,默认的话就是 fzf。如果你使用 Fish shell,需要 2.7.0 以上才支持该功能。
## 快速回到父目录
`"-b"` 选项可以快速回到某一级父目录,避免重复的输入 "cd ../../.."。
- **(没有参数)** `cd` 到项目根目录:
使用 `"z -b"` 后面不跟任何参数,z.lua 会寻找当前项目的 checkout 目录(有 `.git`/`.hg`/`.svn` 的地方) 然后 `cd` 过去。
- **(单个参数)** `cd` 到离当前目录最近的以关键字开头的父目录:
假设你在 `/home/user/project/src/org/main/site/utils/file/reader/whatever` 然后你想快速回到 `site` 目录,
只需要输入:`z -b site`
实际上,可简化为 `z -b <开头的几个字母>` 比如 `z -b s` or `z -b si`。
如果当前存在多级父目录同时包含你输入的关键词,`z -b xxx` 会将你到离你最近的那一层父目录。
- **(两个参数)** 将当前路径中的第一个关键词替换为第二个关键词。
为了使用简便,我们继续将 `z -b` 取个别名成 `zb`
```bash
# 一直向上退到项目根目录(就是里面有一个 .git 目录的地方)
~/github/lorem/src/public$ zb
=> cd ~/github/lorem
# cd 到第一个以 g 开头的父目录
~/github/vimium/src/public$ zb g
=> cd ~/github
# 将 jekyll 替换为 ghost
~/github/jekyll/test$ zb jekyll ghost
=> cd ~/github/ghost/test
```
向后跳转同样也支持环境变量 `$_ZL_ECHO`(用来显示跳转结果),这样为搭配其他工具提供了可能性(并不需要改变当前工作目录):
```bash
# 假设我们位于 ~/github/vim/src/libvterm
# 打开 $_ZL_ECHO 用于在每次跳转后调用一次 pwd 显示当前目录
$ _ZL_ECHO=1
# 看看我项目根目录(有 .git 那个)目录里有什么?
$ ls -l `zb`
=> ls -l ~/github/vim
# 检查 "<项目根目录>/logs" 下面的日志
$ tail -f `zb`/logs/error.log
=> tail -f ~/github/vim/logs/error.log
# 查看一下某一级父目录里有些啥
$ ls -l `zb git`
=> ls -l ~/github
```
## Tips
推荐一些常用的命令别名:
@@ -256,6 +317,7 @@ PS:你可以使用 `$_ZL_FZF` 环境变量来精确指明 fzf 的可执行路
alias zc='z -c' # 严格匹配当前路径的子路径
alias zz='z -i' # 使用交互式选择模式
alias zf='z -I' # 使用 fzf 对多个结果进行选择
alias zb='z -b' # 快速回到父目录
```
+67 -6
View File
@@ -26,7 +26,8 @@ For example, `z foo bar` would match `/foo/bar` but not `/bar/foo`.
- New "$_ZL_ADD_ONCE" to allow updating database only if `$PWD` changed.
- Enhanced matching mode with "$_ZL_MATCH_MODE" set to 1.
- Interactive selection enables you to choose where to go before cd.
- Support fzf for selecting from multiple results.
- Support fzf for selecting from multiple results (optional).
- Quickly go back to a parent directory instead of typing "cd ../../..".
## Examples
@@ -241,7 +242,7 @@ From version 1.1.0, a new option `"-I"` will allow you to use fzf to select when
When we use `"z -I vim"`12 paths contains keyword "vim" has been matched and ordered by their frecent value, the higher frecent comes with the higher rank. Then without cd to the highest ranked path, z.lua passes all the candidates to fzf. And you can use fzf to select where you want to go, or ESC to quit.
Of course, you can always give more keywords to `z` command to match your destination precisely. This feature provide you another way to do that.
Of course, you can always give more keywords to `z` command to match your destination precisely. `"z -I"` is similar to `"z -i"`, but use fzf. Both `"-i"` and `"-I"` provide you another way for path navigation.
Usually, `z -I` can be aliased to `zf` (z + fuzzy finder) for convenience. If there are only one path matched, `z -I` will jump to it directly, fzf will only be invoked for multiple matches.
@@ -249,6 +250,62 @@ Usually, `z -I` can be aliased to `zf` (z + fuzzy finder) for convenience. If th
NOTE: For fish shell, this feature requires fish 2.7.0 or above. You can specify fzf executable in `$_ZL_FZF` environment variable, `"fzf"` will be called by default.
## Jump Backwards
New option `"-b"` can quickly go back to a specific parent directory in bash instead of typing "cd ../../.." redundantly.
- **(No argument)** `cd` into the project root:
Use `z -b` with no argument, it will look for the project (checkout) directory (the one with `.git`/`.hg`/`.svn` in it) and then `cd` into it.
- **(One argument)** `cd` into the closest parent having its name begin with whatever the value you passed in:
If you are in this path `/home/user/project/src/org/main/site/utils/file/reader/whatever` and you want to go to `site` directory quickly,
then just type: `z -b site`
In fact, You can simply type `z -b <starting few letters>` like `z -b s` or `z -b si`.
If there are more than one directories with same name up in the hierarchy, `z -b` will take you to the closest.
- **(Two arguments)** replace the first value with the second one (in the current path).
Let's start by alising `z -b` to `zb`:
```bash
# go all the way up to the project root (in this case, the one that has .git in it)
~/github/lorem/src/public$ zb
=> cd ~/github/lorem
# cd into to the first parent directory named g*
~/github/vimium/src/public$ zb g
=> cd ~/github
# substitute jekyll with ghost
~/github/jekyll/test$ zb jekyll ghost
=> cd ~/github/ghost/test
```
Backward jumping can also be used with `$_ZL_ECHO` option (echo $pwd), which makes it possible to combine them with other tools (without actually changing the working directory):
```bash
# Assuming we are in ~/github/vim/src/libvterm
# Enable $_ZL_ECHO to emit a pwd command after cd
$ _ZL_ECHO=1
# see what's in my project root
$ ls -l `zb`
=> ls -l ~/github/vim
# check log in "<project root>/logs"
$ tail -f `zb`/logs/error.log
=> tail -f ~/github/vim/logs/error.log
# list some parent directory
$ ls -l `zb git`
=> ls -l ~/github
```
## Tips
@@ -258,6 +315,7 @@ Recommended aliases you may find useful:
alias zc='z -c' # restrict matches to subdirs of $PWD
alias zz='z -i' # cd with interactive selection
alias zf='z -I' # use fzf to select in multiple matches
alias zb='z -b' # quickly cd to the parent directory
```
@@ -309,6 +367,8 @@ awk -F '\t' '{print $2 "|" $1 "|" 0}' $FN >> ~/.zlua
## History
- 1.3.0 (2019-02-04): Backward jumping, prevent "cd ../../.." repeatly.
- 1.2.0 (2019-02-03): Upgrading string lib and path lib.
- 1.1.0 (2019-02-02): New option '-I' to use fzf to select from multiple matches.
- 1.0.0 (2019-02-01): Fixed minor issues and make it stable.
- 0.5.0 (2019-01-21): supports fish shell (Daniel Lewan).
@@ -319,12 +379,13 @@ awk -F '\t' '{print $2 "|" $1 "|" 0}' $FN >> ~/.zlua
- 0.1.0 (2018-04-30): supports windows cmd, cmder and conemu.
- 0.0.0 (2018-03-21): initial commit, compatible with original z.sh.
## Credit
## Thanks
Releated projects:
Thanks to @rupa for inspiring me to start this project.
Thanks to @vigneshwaranr and @shyiko for inspiring me the backward jumping.
Thanks to @TeddyDD for fish shell porting.
- [rupa/z](https://github.com/rupa/z): origin z.sh implementation
- [JannesMeyer/z.ps](https://github.com/JannesMeyer/z.ps): z for powershell
And many others.
## License
+193
View File
@@ -0,0 +1,193 @@
local zmod = require('z')
local windows = os.path.sep == '\\'
-----------------------------------------------------------------------
-- logo
-----------------------------------------------------------------------
function print_title(text)
print(string.rep('-', 72))
print('-- '.. text)
print(string.rep('-', 72))
end
-----------------------------------------------------------------------
-- os.path.normpath
-----------------------------------------------------------------------
print_title('os.path.normpath')
function assert_posix(path, result)
local x = os.path.normpath(path)
print('[test] normpath: ('..path..') -> (' .. result .. ')')
if x:gsub('\\', '/') ~= result then
print('failed: "' .. x .. '" != "'..result.. '"')
os.exit()
else
print('passed')
print()
end
end
function assert_windows(path, result)
local x = os.path.normpath(path)
print('[test] normpath: ('..path..') -> (' .. result .. ')')
if x ~= result then
print('failed: "' .. x .. '" != "'..result.. '"')
os.exit()
else
print('passed')
print()
end
end
assert_posix("", ".")
assert_posix("/", "/")
assert_posix("///", "/")
assert_posix("///foo/.//bar//", "/foo/bar")
assert_posix("///foo/.//bar//.//..//.//baz", "/foo/baz")
assert_posix("///..//./foo/.//bar", "/foo/bar")
if windows then
assert_windows('A//////././//.//B', 'A\\B')
assert_windows('A/./B', 'A\\B')
assert_windows('A/foo/../B', 'A\\B')
assert_windows('C:A//B', 'C:A\\B')
assert_windows('D:A/./B', 'D:A\\B')
assert_windows('e:A/foo/../B', 'e:A\\B')
assert_windows('C:///A//B', 'C:\\A\\B')
assert_windows('D:///A/./B', 'D:\\A\\B')
assert_windows('e:///A/foo/../B', 'e:\\A\\B')
assert_windows('..', '..')
assert_windows('.', '.')
assert_windows('', '.')
assert_windows('/', '\\')
assert_windows('c:/', 'c:\\')
assert_windows('/../.././..', '\\')
assert_windows('c:/../../..', 'c:\\')
assert_windows('../.././..', '..\\..\\..')
assert_windows('K:../.././..', 'K:..\\..\\..')
assert_windows('C:////a/b', 'C:\\a\\b')
end
print()
-----------------------------------------------------------------------
-- os.path.join
-----------------------------------------------------------------------
print_title('os.path.join')
function assert_join_posix(segments, result, isnt)
print('[test] join: '..zmod.dump(segments)..' -> (' .. result .. ')')
local path = ''
for _, item in ipairs(segments) do
path = os.path.join(path, item)
end
if windows and (not isnt) then
path = path:gsub('\\', '/')
end
if path ~= result then
print('failed: "' .. path .. '"')
os.exit()
else
print('passed')
end
end
function assert_join_windows(segments, result)
assert_join_posix(segments, result, 1)
end
assert_join_posix({"/foo", "bar", "/bar", "baz"}, "/bar/baz")
assert_join_posix({"/foo", "bar", "baz"}, "/foo/bar/baz")
assert_join_posix({"/foo/", "bar/", "baz/"}, "/foo/bar/baz/")
if windows then
assert_join_windows({""}, '')
assert_join_windows({"", "", ""}, '')
assert_join_windows({"a"}, 'a')
assert_join_windows({"/a"}, '/a')
assert_join_windows({"\\a"}, '\\a')
assert_join_windows({"a:"}, 'a:')
assert_join_windows({"a:", "\\b"}, 'a:\\b')
assert_join_windows({"a", "\\b"}, '\\b')
assert_join_windows({"a", "b", "c"}, 'a\\b\\c')
assert_join_windows({"a\\", "b", "c"}, 'a\\b\\c')
assert_join_windows({"a", "b\\", "c"}, 'a\\b\\c')
assert_join_windows({"a", "b", "\\c"}, '\\c')
assert_join_windows({"d:\\", "\\pleep"}, 'd:\\pleep')
assert_join_windows({"d:\\", "a", "b"}, 'd:\\a\\b')
assert_join_windows({'', 'a'}, 'a')
assert_join_windows({'', '', '', '', 'a'}, 'a')
assert_join_windows({'a', ''}, 'a\\')
assert_join_windows({'a', '', '', '', ''}, 'a\\')
assert_join_windows({'a\\', ''}, 'a\\')
assert_join_windows({'a\\', '', '', '', ''}, 'a\\')
assert_join_windows({'a/', ''}, 'a/')
assert_join_windows({'a/b', 'x/y'}, 'a/b\\x/y')
assert_join_windows({'/a/b', 'x/y'}, '/a/b\\x/y')
assert_join_windows({'/a/b/', 'x/y'}, '/a/b/x/y')
assert_join_windows({'c:', 'x/y'}, 'c:x/y')
assert_join_windows({'c:a/b', 'x/y'}, 'c:a/b\\x/y')
assert_join_windows({'c:a/b/', 'x/y'}, 'c:a/b/x/y')
assert_join_windows({'c:/', 'x/y'}, 'c:/x/y')
assert_join_windows({'c:/a/b', 'x/y'}, 'c:/a/b\\x/y')
assert_join_windows({'c:/a/b/', 'x/y'}, 'c:/a/b/x/y')
assert_join_windows({'a/b', '/x/y'}, '/x/y')
assert_join_windows({'/a/b', '/x/y'}, '/x/y')
assert_join_windows({'c:', '/x/y'}, 'c:/x/y')
assert_join_windows({'c:a/b', '/x/y'}, 'c:/x/y')
assert_join_windows({'c:/', '/x/y'}, 'c:/x/y')
assert_join_windows({'c:/a/b', '/x/y'}, 'c:/x/y')
assert_join_windows({'c:', 'C:x/y'}, 'C:x/y')
assert_join_windows({'c:a/b', 'C:x/y'}, 'C:a/b\\x/y')
assert_join_windows({'c:/', 'C:x/y'}, 'C:/x/y')
assert_join_windows({'c:/a/b', 'C:x/y'}, 'C:/a/b\\x/y')
for _, x in ipairs({'', 'a/b', '/a/b', 'c:', 'c:a/b', 'c:/', 'c:/a/b'}) do
for _, y in ipairs({'d:', 'd:x/y', 'd:/', 'd:/x/y'}) do
assert_join_windows({x, y}, y)
end
end
end
print()
-----------------------------------------------------------------------
-- os.path.split
-----------------------------------------------------------------------
print_title('os.path.split')
function assert_split(path, sep1, sep2)
print('[test] split: "' .. path ..'" -> ("' .. sep1 .. '", "' .. sep2 .. '")')
local x, y = os.path.split(path)
if x ~= sep1 or y ~= sep2 then
print('failed: ("'..x..'", "'..y..'")')
os.exit()
else
print('passed')
end
end
assert_split("", "", "")
assert_split(".", "", ".")
assert_split("/foo/bar", "/foo", "bar")
assert_split("/", "/", "")
assert_split("foo", "", "foo")
assert_split("////foo", "////", "foo")
assert_split("//foo//bar", "//foo", "bar")
if windows then
assert_split("c:\\foo\\bar", 'c:\\foo', 'bar')
assert_split("\\\\conky\\mountpoint\\foo\\bar", '\\\\conky\\mountpoint\\foo', 'bar')
assert_split("c:\\", "c:\\", '')
assert_split("c:/", "c:/", '')
assert_split("c:test", "c:", 'test')
assert_split("c:", "c:", '')
-- assert_split("\\\\conky\\mountpoint\\", "\\\\conky\\mountpoint\\", '')
end
+317 -35
View File
@@ -4,7 +4,7 @@
-- z.lua - z.sh implementation in lua, by skywind 2018, 2019
-- Licensed under MIT license.
--
-- Version 1.1.0, Last Modified: 2019/02/02 14:51
-- Version 1.3.0, Last Modified: 2019/02/04 00:06
--
-- * 10x times faster than fasd and autojump
-- * 3x times faster than rupa/z
@@ -87,6 +87,7 @@ local in_module = pcall(debug.getlocal, 4, 1) and true or false
local utils = {}
os.path = {}
os.argv = arg ~= nil and arg or {}
os.path.sep = windows and '\\' or '/'
-----------------------------------------------------------------------
@@ -110,7 +111,7 @@ os.LOG_NAME = os.getenv('_ZL_LOG_NAME')
-----------------------------------------------------------------------
-- split string
-- string lib
-----------------------------------------------------------------------
function string:split(sSeparator, nMax, bRegexp)
assert(sSeparator ~= '')
@@ -120,23 +121,21 @@ function string:split(sSeparator, nMax, bRegexp)
local bPlain = not bRegexp
nMax = nMax or -1
local nField, nStart = 1, 1
local nFirst,nLast = self:find(sSeparator, nStart, bPlain)
local nFirst, nLast = self:find(sSeparator, nStart, bPlain)
while nFirst and nMax ~= 0 do
aRecord[nField] = self:sub(nStart, nFirst-1)
nField = nField+1
nStart = nLast+1
nFirst,nLast = self:find(sSeparator, nStart, bPlain)
nMax = nMax-1
aRecord[nField] = self:sub(nStart, nFirst - 1)
nField = nField + 1
nStart = nLast + 1
nFirst, nLast = self:find(sSeparator, nStart, bPlain)
nMax = nMax - 1
end
aRecord[nField] = self:sub(nStart)
else
aRecord[1] = ''
end
return aRecord
end
-----------------------------------------------------------------------
-- string starts with
-----------------------------------------------------------------------
function string:startswith(text)
local size = text:len()
if self:sub(1, size) == text then
@@ -145,6 +144,52 @@ function string:startswith(text)
return false
end
function string:lstrip()
if self == nil then return nil end
local s = self:gsub('^%s+', '')
return s
end
function string:rstrip()
if self == nil then return nil end
local s = self:gsub('%s+$', '')
return s
end
function string:strip()
return self:lstrip():rstrip()
end
function string:rfind(key)
if keyword == '' then
return self:len(), 0
end
local length = self:len()
local start, ends = self:reverse():find(key:reverse())
if start == nil then
return nil
end
return (length - ends + 1), (length - start + 1)
end
function string:join(parts)
if parts == nil or #parts == 0 then
return ''
end
local size = #parts
local text = ''
local index = 1
while index <= size do
if index == 1 then
text = text .. parts[index]
else
text = text .. self .. parts[index]
end
index = index + 1
end
return text
end
-----------------------------------------------------------------------
-- table size
@@ -297,15 +342,28 @@ end
-----------------------------------------------------------------------
-- get absolute path
-- absolute path (simulated)
-----------------------------------------------------------------------
function os.path.absolute(path)
local pwd = os.pwd()
return os.path.normpath(os.path.join(pwd, path))
end
-----------------------------------------------------------------------
-- absolute path (system call, can fall back to os.path.absolute)
-----------------------------------------------------------------------
function os.path.abspath(path)
if path == '' then path = '.' end
if windows then
local script = 'FOR /f "delims=" %%i IN ("%s") DO @echo %%~fi'
local script = string.format(script, path)
local script = 'cmd.exe /C ' .. script .. ' 2> nul'
local output = os.call(script)
return output:gsub('%s$', '')
local test = output:gsub('%s$', '')
if test ~= nil and test ~= '' then
return test
end
else
local test = os.path.which('realpath')
if test ~= nil and test ~= '' then
@@ -314,15 +372,15 @@ function os.path.abspath(path)
return test
end
end
if os.path.isdir(path) then
if os.path.exists('/bin/sh') and os.path.exists('/bin/pwd') then
local cmd = "/bin/sh -c 'cd \"" ..path .."\"; /bin/pwd'"
test = os.call(cmd)
if os.path.isdir(path) then
if os.path.exists('/bin/sh') and os.path.exists('/bin/pwd') then
local cmd = "/bin/sh -c 'cd \"" ..path .."\"; /bin/pwd'"
test = os.call(cmd)
if test ~= nil and test ~= '' then
return test
end
end
end
end
end
local test = os.path.which('perl')
if test ~= nil and test ~= '' then
local s = 'perl -MCwd -e "print Cwd::realpath(\\$ARGV[0])" \'%s\''
@@ -342,6 +400,7 @@ function os.path.abspath(path)
end
end
end
return os.path.absolute(path)
end
@@ -379,20 +438,20 @@ end
-----------------------------------------------------------------------
-- is absolute path
-----------------------------------------------------------------------
function os.path.isabs(pathname)
local h1 = pathname:sub(1, 1)
if windows then
local h2 = pathname:sub(2, 2)
local h3 = pathname:sub(3, 3)
if h1 == '/' or h1 == '\\' then
return true
end
if h2 == ':' and (h3 == '/' or h3 == '\\') then
return true
end
elseif h1 == '/' then
function os.path.isabs(path)
if path == nil or path == '' then
return false
elseif path:sub(1, 1) == '/' then
return true
end
if windows then
local head = path:sub(1, 1)
if head == '\\' then
return true
elseif path:match('^%a:[/\\]') ~= nil then
return true
end
end
return false
end
@@ -411,6 +470,165 @@ function os.path.norm(pathname)
end
-----------------------------------------------------------------------
-- normalize . and ..
-----------------------------------------------------------------------
function os.path.normpath(path)
if os.path.sep ~= '/' then
path = path:gsub('\\', '/')
end
path = path:gsub('/+', '/')
local srcpath = path
local basedir = ''
local isabs = false
if windows and path:sub(2, 2) == ':' then
basedir = path:sub(1, 2)
path = path:sub(3, -1)
end
if path:sub(1, 1) == '/' then
basedir = basedir .. '/'
isabs = true
path = path:sub(2, -1)
end
local parts = path:split('/')
local output = {}
for _, path in ipairs(parts) do
if path == '.' or path == '' then
elseif path == '..' then
local size = #output
if size == 0 then
if not isabs then
table.insert(output, '..')
end
elseif output[size] == '..' then
table.insert(output, '..')
else
table.remove(output, size)
end
else
table.insert(output, path)
end
end
path = basedir .. string.join('/', output)
if windows then path = path:gsub('/', '\\') end
return path == '' and '.' or path
end
-----------------------------------------------------------------------
-- join two path
-----------------------------------------------------------------------
function os.path.join(path1, path2)
if path1 == nil or path1 == '' then
if path2 == nil or path2 == '' then
return ''
else
return path2
end
elseif path2 == nil or path2 == '' then
local head = path1:sub(-1, -1)
if head == '/' or (windows and head == '\\') then
return path1
end
return path1 .. os.path.sep
elseif os.path.isabs(path2) then
if windows then
local head = path2:sub(1, 1)
if head == '/' or head == '\\' then
if path1:match('^%a:') then
return path1:sub(1, 2) .. path2
end
end
end
return path2
elseif windows then
local d1 = path1:match('^%a:') and path1:sub(1, 2) or ''
local d2 = path2:match('^%a:') and path2:sub(1, 2) or ''
if d1 ~= '' then
if d2 ~= '' then
if d1:lower() == d2:lower() then
return d2 .. os.path.join(path1:sub(3), path2:sub(3))
else
return path2
end
end
elseif d2 ~= '' then
return path2
end
end
local postsep = true
local len1 = path1:len()
local len2 = path2:len()
if path1:sub(-1, -1) == '/' then
postsep = false
elseif windows then
if path1:sub(-1, -1) == '\\' then
postsep = false
elseif len1 == 2 and path1:sub(2, 2) == ':' then
postsep = false
end
end
if postsep then
return path1 .. os.path.sep .. path2
else
return path1 .. path2
end
end
-----------------------------------------------------------------------
-- split
-----------------------------------------------------------------------
function os.path.split(path)
if path == '' then
return '', ''
end
local pos = path:rfind('/')
if os.path.sep == '\\' then
local p2 = path:rfind('\\')
if pos == nil and p2 ~= nil then
pos = p2
elseif p1 ~= nil and p2 ~= nil then
pos = (pos < p2) and pos or p2
end
if path:match('^%a:[/\\]') and pos == nil then
return path:sub(1, 2), path:sub(3)
end
end
if pos == nil then
if windows then
local drive = path:match('^%a:') and path:sub(1, 2) or ''
if drive ~= '' then
return path:sub(1, 2), path:sub(3)
end
end
return '', path
elseif pos == 1 then
return path:sub(1, 1), path:sub(2)
elseif windows then
local drive = path:match('^%a:') and path:sub(1, 2) or ''
if pos == 3 then
return path:sub(1, 3), path:sub(4)
end
end
local head = path:sub(1, pos)
local tail = path:sub(pos + 1)
if not windows then
local test = string.rep('/', head:len())
if head ~= test then
head = head:gsub('/+$', '')
end
else
local t1 = string.rep('/', head:len())
local t2 = string.rep('\\', head:len())
if head ~= t1 and head ~= t2 then
head = head:gsub('[/\\]+$', '')
end
end
return head, tail
end
-----------------------------------------------------------------------
-- check subdir
-----------------------------------------------------------------------
@@ -1017,6 +1235,9 @@ function z_match(patterns, method, subdir)
end
table.sort(M, function (a, b) return a.score > b.score end)
local pwd = (PWD == nil or PWD == '') and os.getenv('PWD') or PWD
if pwd == nil or pwd == '' then
pwd = os.pwd()
end
if pwd ~= '' and pwd ~= nil then
if subdir then
local N = {}
@@ -1187,9 +1408,70 @@ end
-----------------------------------------------------------------------
-- cd to parent directories which contains keyword
-- find_vcs_root
-----------------------------------------------------------------------
function cd_backward(args, options)
function find_vcs_root(path)
local markers = os.getenv('_ZL_ROOT_MARKERS')
local markers = markers and markers or '.git,.svn,.hg,.root'
local markers = string.split(markers, ',')
path = os.path.absolute(path)
while true do
for _, marker in ipairs(markers) do
local test = os.path.join(path, marker)
if os.path.exists(test) then
return path
end
end
local parent, _ = os.path.split(path)
if path == parent then break end
path = parent
end
return nil
end
-----------------------------------------------------------------------
-- cd to parent directories which contains keyword
-- #args == 0 -> returns to vcs root
-- #args == 1 -> returns to parent dir starts with args[1]
-- #args == 2 -> returns string.replace($PWD, args[1], args[2])
-----------------------------------------------------------------------
function cd_backward(args, options, pwd)
local nargs = #args
local pwd = (pwd ~= nil) and pwd or os.pwd()
if nargs == 0 then
return find_vcs_root(pwd)
elseif nargs == 1 then
local test = windows and pwd:gsub('\\', '/') or pwd
local key = '/' .. args[1]
if not key:match('%u') then
test = test:lower()
end
local pos, _ = test:rfind(key)
if not pos then
return nil
end
local ends = test:find('/', pos + key:len())
if not ends then
ends = test:len()
end
local path = pwd:sub(1, (not ends) and test:len() or ends)
return os.path.normpath(path)
else
local test = windows and pwd:gsub('\\', '/') or pwd
local src = args[1]
local dst = args[2]
if not src:match('%u') then
test = test:lower()
end
local start, ends = test:rfind(src)
if not start then
return pwd
end
local lhs = pwd:sub(1, start - 1)
local rhs = pwd:sub(ends + 1)
return lhs .. dst .. rhs
end
end
@@ -1247,7 +1529,7 @@ function main(argv)
elseif options['-d'] then
path = cd_detour(args, options)
elseif #args == 0 then
path = os.path.expand('~')
path = nil
else
path = z_cd(args)
if path == nil and Z_MATCHMODE ~= 0 then
+1
View File
@@ -27,5 +27,6 @@ eval "$($ZLUA_EXEC $ZLUA_SCRIPT --init zsh once enhanced)"
alias zz='z -i'
alias zc='z -c'
alias zf='z -I'
alias zb='z -b'
alias zzc='zz -c'