From 60779c1d2b9a58a88d3c00e319b21dfec7d08926 Mon Sep 17 00:00:00 2001 From: skywind3000 Date: Sun, 3 Feb 2019 12:43:10 +0800 Subject: [PATCH 01/12] update doc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 231da5e..5c69ac2 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,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. From 589e4748142f1f437d6d50581023bc55b5266847 Mon Sep 17 00:00:00 2001 From: skywind3000 Date: Sun, 3 Feb 2019 17:46:42 +0800 Subject: [PATCH 02/12] refactor string lib and path lib --- z.lua | 169 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 145 insertions(+), 24 deletions(-) diff --git a/z.lua b/z.lua index cf06a53..92daf63 100755 --- a/z.lua +++ b/z.lua @@ -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.2.0, Last Modified: 2019/02/03 17:46 -- -- * 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 @@ -379,20 +424,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 +456,82 @@ 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 path2 == nil or path2 == '' then + return path1 + elseif os.path.isabs(path2) then + return path2 + elseif path1 == nil or path1 == '' then + return path2 + 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 + + ----------------------------------------------------------------------- -- check subdir ----------------------------------------------------------------------- From 5f5df0418b2735268283c56447ad6315e2c03f5d Mon Sep 17 00:00:00 2001 From: skywind3000 Date: Sun, 3 Feb 2019 18:57:37 +0800 Subject: [PATCH 03/12] add test --- test_path.lua | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++ z.lua | 60 ++++++++++++++++++-- 2 files changed, 206 insertions(+), 6 deletions(-) create mode 100644 test_path.lua diff --git a/test_path.lua b/test_path.lua new file mode 100644 index 0000000..321c2ce --- /dev/null +++ b/test_path.lua @@ -0,0 +1,152 @@ +local zmod = require('z') +local windows = os.path.sep == '\\' + + +----------------------------------------------------------------------- +-- test 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 test_normpath_posix() + 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") +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 + +function test_normpath_windows() + 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 + +test_normpath_posix() + +if windows then + test_normpath_windows() +end + + +----------------------------------------------------------------------- +-- test 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 + +function test_join_posix() + 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/") +end + +function test_join_windows() + 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') +end + +test_join_posix() + +if windows then + test_join_windows() +end + diff --git a/z.lua b/z.lua index 92daf63..bce3248 100755 --- a/z.lua +++ b/z.lua @@ -342,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 @@ -387,6 +400,7 @@ function os.path.abspath(path) end end end + return os.path.absolute(path) end @@ -505,12 +519,46 @@ end -- join two path ----------------------------------------------------------------------- function os.path.join(path1, path2) - if path2 == nil or path2 == '' then - return path1 + 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 path1 == nil or path1 == '' then - return path2 + elseif windows then + local d1 = path1:sub(1, 2) + local d2 = path2:sub(1, 2) + if not path1:match('^%a:') then + d1 = '' + end + if not path2:match('^%a:') then + d2 = '' + end + 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 + end end local postsep = true local len1 = path1:len() From 99529516fb907d2482c0d7c13252953239e7a270 Mon Sep 17 00:00:00 2001 From: skywind3000 Date: Sun, 3 Feb 2019 19:02:05 +0800 Subject: [PATCH 04/12] passes all tests in path_join --- test_path.lua | 6 ++++++ z.lua | 12 ++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/test_path.lua b/test_path.lua index 321c2ce..1128aca 100644 --- a/test_path.lua +++ b/test_path.lua @@ -142,6 +142,12 @@ function test_join_windows() 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 test_join_posix() diff --git a/z.lua b/z.lua index bce3248..0fc22c4 100755 --- a/z.lua +++ b/z.lua @@ -542,14 +542,8 @@ function os.path.join(path1, path2) end return path2 elseif windows then - local d1 = path1:sub(1, 2) - local d2 = path2:sub(1, 2) - if not path1:match('^%a:') then - d1 = '' - end - if not path2:match('^%a:') then - d2 = '' - end + 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 @@ -558,6 +552,8 @@ function os.path.join(path1, path2) return path2 end end + elseif d2 ~= '' then + return path2 end end local postsep = true From 588b2fc4687de80184a9dd7ff7272f3eda1dca4d Mon Sep 17 00:00:00 2001 From: skywind3000 Date: Sun, 3 Feb 2019 19:34:01 +0800 Subject: [PATCH 05/12] passed all path_join tests --- z.lua | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/z.lua b/z.lua index 0fc22c4..f967309 100755 --- a/z.lua +++ b/z.lua @@ -576,6 +576,56 @@ function os.path.join(path1, path2) 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) + local test = string.rep('/', head:len()) + if head ~= test then + head = head:gsub('/+$', '') + elseif windows then + test = string.rep('\\', head:len()) + if head ~= test then + head = head:gsub('\\+$', '') + end + end + return head, tail +end + + ----------------------------------------------------------------------- -- check subdir ----------------------------------------------------------------------- From 8274913b022ebb0ba79d5f6028e1d7bb1e966627 Mon Sep 17 00:00:00 2001 From: skywind3000 Date: Sun, 3 Feb 2019 20:19:14 +0800 Subject: [PATCH 06/12] passes all the tests for os.path.split --- test_path.lua | 87 ++++++++++++++++++++++++++++++++++++--------------- z.lua | 15 +++++---- 2 files changed, 70 insertions(+), 32 deletions(-) diff --git a/test_path.lua b/test_path.lua index 1128aca..2465ffb 100644 --- a/test_path.lua +++ b/test_path.lua @@ -1,10 +1,21 @@ 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 + ----------------------------------------------------------------------- --- test normpath +-- os.path.normpath ----------------------------------------------------------------------- +print_title('os.path.normpath') + function assert_posix(path, result) local x = os.path.normpath(path) print('[test] normpath: ('..path..') -> (' .. result .. ')') @@ -17,15 +28,6 @@ function assert_posix(path, result) end end -function test_normpath_posix() - 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") -end - function assert_windows(path, result) local x = os.path.normpath(path) print('[test] normpath: ('..path..') -> (' .. result .. ')') @@ -38,7 +40,14 @@ function assert_windows(path, result) end end -function test_normpath_windows() +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') @@ -60,16 +69,14 @@ function test_normpath_windows() assert_windows('C:////a/b', 'C:\\a\\b') end -test_normpath_posix() - -if windows then - test_normpath_windows() -end +print() ----------------------------------------------------------------------- --- test path join +-- os.path.join ----------------------------------------------------------------------- +print_title('os.path.join') + function assert_join_posix(segments, result, isnt) print('[test] join: '..zmod.dump(segments)..' -> (' .. result .. ')') local path = '' @@ -91,13 +98,11 @@ function assert_join_windows(segments, result) assert_join_posix(segments, result, 1) end -function test_join_posix() - 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/") -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/") -function test_join_windows() +if windows then assert_join_windows({""}, '') assert_join_windows({"", "", ""}, '') assert_join_windows({"a"}, 'a') @@ -150,9 +155,39 @@ function test_join_windows() end end -test_join_posix() +print() -if windows then - test_join_windows() + +----------------------------------------------------------------------- +-- 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 diff --git a/z.lua b/z.lua index f967309..ba15beb 100755 --- a/z.lua +++ b/z.lua @@ -613,13 +613,16 @@ function os.path.split(path) end local head = path:sub(1, pos) local tail = path:sub(pos + 1) - local test = string.rep('/', head:len()) - if head ~= test then - head = head:gsub('/+$', '') - elseif windows then - test = string.rep('\\', head:len()) + if not windows then + local test = string.rep('/', head:len()) if head ~= test then - head = head:gsub('\\+$', '') + 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 From 7f902b28aafbd5a09c169e6c7da2b483a634f894 Mon Sep 17 00:00:00 2001 From: skywind3000 Date: Sun, 3 Feb 2019 21:16:24 +0800 Subject: [PATCH 07/12] update: os.path.abspath --- z.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/z.lua b/z.lua index ba15beb..560a7db 100755 --- a/z.lua +++ b/z.lua @@ -4,7 +4,7 @@ -- z.lua - z.sh implementation in lua, by skywind 2018, 2019 -- Licensed under MIT license. -- --- Version 1.2.0, Last Modified: 2019/02/03 17:46 +-- Version 1.2.1, Last Modified: 2019/02/03 21:15 -- -- * 10x times faster than fasd and autojump -- * 3x times faster than rupa/z @@ -372,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\'' @@ -1465,7 +1465,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 From c597d1d6220502cd9ace76207424f2c9ee59bb8c Mon Sep 17 00:00:00 2001 From: skywind3000 Date: Mon, 4 Feb 2019 00:07:38 +0800 Subject: [PATCH 08/12] new option: '-b' to jump backwards --- z.lua | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/z.lua b/z.lua index 560a7db..ee8ce78 100755 --- a/z.lua +++ b/z.lua @@ -4,7 +4,7 @@ -- z.lua - z.sh implementation in lua, by skywind 2018, 2019 -- Licensed under MIT license. -- --- Version 1.2.1, Last Modified: 2019/02/03 21:15 +-- Version 1.3.0, Last Modified: 2019/02/04 00:06 -- -- * 10x times faster than fasd and autojump -- * 3x times faster than rupa/z @@ -1405,9 +1405,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 From 0b262ce614cfe2d6ae3aa96eb1ede0713b148f89 Mon Sep 17 00:00:00 2001 From: skywind3000 Date: Mon, 4 Feb 2019 00:56:59 +0800 Subject: [PATCH 09/12] update doc --- README.cn.md | 3 ++- README.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/README.cn.md b/README.cn.md index 51d7186..04cc384 100644 --- a/README.cn.md +++ b/README.cn.md @@ -20,7 +20,8 @@ z.lua 是一个快速路径切换工具,它会跟踪你在 shell 下访问过 - 新增:环境变量 "$_ZL_ADD_ONCE" 设成 1 的话性仅当前路径改变时才更新数据库。 - 新增:增强匹配模式,将环境变量 "$_ZL_MATCH_MODE" 设置成 1 可以启用。 - 新增:交互选择模式,如果有多个匹配结果的话,跳转前允许你进行选择。 -- 新增:支持 fzf 来进行多结果筛选,见后面文档。 +- 新增:支持 fzf 来进行多结果筛选,见后面文档。 +- 新增:快速跳转到父目录,代替反复 “cd ../../.." 。 ## Examples diff --git a/README.md b/README.md index 5c69ac2..9bddaf4 100644 --- a/README.md +++ b/README.md @@ -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 @@ -249,6 +250,64 @@ 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"` to 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 ` 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 +``` + +## Echo target result + +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 "/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 +317,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' # cd backwards to parent directory ``` @@ -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 From ab87d65b05f6a8c6d721201495ba9cd5a561ece9 Mon Sep 17 00:00:00 2001 From: skywind3000 Date: Mon, 4 Feb 2019 01:14:27 +0800 Subject: [PATCH 10/12] update doc --- README.cn.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 6 +++--- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/README.cn.md b/README.cn.md index 04cc384..00f5b04 100644 --- a/README.cn.md +++ b/README.cn.md @@ -249,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 推荐一些常用的命令别名: diff --git a/README.md b/README.md index 9bddaf4..336ee3f 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,7 @@ NOTE: For fish shell, this feature requires fish 2.7.0 or above. You can specify ## Jump Backwards -New option `"-b"` to quickly go back to a specific parent directory in bash instead of typing "cd ../../.." redundantly. +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: @@ -285,8 +285,6 @@ Let's start by alising `z -b` to `zb`: => cd ~/github/ghost/test ``` -## Echo target result - 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 @@ -369,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). From 13c63e87dd98421a807ce07d9855e9a51f4e38cb Mon Sep 17 00:00:00 2001 From: skywind3000 Date: Mon, 4 Feb 2019 01:17:05 +0800 Subject: [PATCH 11/12] upgrade zsh plugin entry, add `z -b` alias --- README.cn.md | 1 + README.md | 2 +- z.lua.plugin.zsh | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.cn.md b/README.cn.md index 00f5b04..e2f4d0b 100644 --- a/README.cn.md +++ b/README.cn.md @@ -317,6 +317,7 @@ $ ls -l `zb git` alias zc='z -c' # 严格匹配当前路径的子路径 alias zz='z -i' # 使用交互式选择模式 alias zf='z -I' # 使用 fzf 对多个结果进行选择 +alias zb='z -b' # 快速回到父目录 ``` diff --git a/README.md b/README.md index 336ee3f..4e225a9 100644 --- a/README.md +++ b/README.md @@ -315,7 +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' # cd backwards to parent directory +alias zb='z -b' # quickly cd to the parent directory ``` diff --git a/z.lua.plugin.zsh b/z.lua.plugin.zsh index 72210ad..1110586 100644 --- a/z.lua.plugin.zsh +++ b/z.lua.plugin.zsh @@ -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' From 6cfe10c6320b1a68e67b311a34e801f0a9d66c08 Mon Sep 17 00:00:00 2001 From: skywind3000 Date: Mon, 4 Feb 2019 01:47:21 +0800 Subject: [PATCH 12/12] fixed pwd --- z.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/z.lua b/z.lua index ee8ce78..c369139 100755 --- a/z.lua +++ b/z.lua @@ -1235,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 = {}