|
-- Porting z.sh for NYAOS 3.x |
|
-- |
|
-- Maintainer: DeaR <[email protected]> |
|
-- Last Change: 13-Aug-2013. |
|
-- License: MIT License {{{ |
|
-- Copyright (c) 2013 DeaR <[email protected]> |
|
-- |
|
-- Permission is hereby granted, free of charge, to any person obtaining a |
|
-- copy of this software and associated documentation files (the |
|
-- "Software"), to deal in the Software without restriction, including |
|
-- without limitation the rights to use, copy, modify, merge, publish, |
|
-- distribute, sublicense, and/or sell copies of the Software, and to |
|
-- permit persons to whom the Software is furnished to do so, subject to |
|
-- the following conditions: |
|
-- |
|
-- The above copyright notice and this permission notice shall be included |
|
-- in all copies or substantial portions of the Software. |
|
-- |
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|
-- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|
-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|
-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
|
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
|
-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
-- }}} |
|
|
|
-- Copyright (c) 2009 rupa deadwyler under the WTFPL license |
|
-- |
|
-- maintains a jump-list of the directories you actually use |
|
-- |
|
-- INSTALL: |
|
-- * put something like this in your .bashrc/.zshrc: |
|
-- . /path/to/z.sh |
|
-- * cd around for a while to build up the db |
|
-- * PROFIT!! |
|
-- * optionally: |
|
-- set $_Z_CMD in .bashrc/.zshrc to change the command (default z). |
|
-- set $_Z_DATA in .bashrc/.zshrc to change the datafile (default ~/.z). |
|
-- set $_Z_NO_RESOLVE_SYMLINKS to prevent symlink resolution. |
|
-- set $_Z_NO_PROMPT_COMMAND if you're handling PROMPT_COMMAND yourself. |
|
-- set $_Z_EXCLUDE_DIRS to an array of directories to exclude. |
|
-- |
|
-- 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 all dirs matching foo (by frecency) |
|
-- * z -c foo # restrict matches to subdirs of $PWD |
|
|
|
local function _init() |
|
local home = (os.getenv('HOME') or os.getenv('USERPROFILE')):gsub('/', '\\') |
|
local datafile = os.getenv('_Z_DATA') or (home .. '\\_z') |
|
local stat = nyaos.stat(datafile) |
|
if stat and stat.directory then |
|
print('ERROR: z.lua\039s datafile (' .. datafile .. ') is a directory.') |
|
end |
|
end |
|
_init() |
|
|
|
function _z(...) |
|
function _shortest_match(s1, s2) |
|
local ret = s1 |
|
for i = 1, (#s1 < #s2 and #s1 or #s2) do |
|
if string.sub(s1, 1, i):lower() ~= string.sub(s2, 1, i):lower() then |
|
return ret |
|
end |
|
ret = string.sub(s1, 1, i) |
|
end |
|
return ret |
|
end |
|
|
|
local arg = {...} |
|
local home = (os.getenv('HOME') or os.getenv('USERPROFILE')):gsub('/', '\\') |
|
local datafile = os.getenv('_Z_DATA') or (home .. '\\_z') |
|
|
|
-- bail out if we don't own ~/_z (we're another user but our ENV is still set) |
|
if not nyaos.access(datafile, 0) then |
|
local f = io.open(datafile, 'w') |
|
f:close() |
|
elseif not nyaos.access(datafile, 2) then |
|
return |
|
end |
|
|
|
if arg[1] == '--add' then |
|
-- add entries |
|
local arg2 = arg[2]:gsub('/', '\\') |
|
|
|
-- $HOME isn't worth matching |
|
if arg2 == home then |
|
return |
|
end |
|
|
|
-- don't track excluded dirs |
|
for exclude in string.gmatch(os.getenv('_Z_EXCLUDE_DIRS') or '', '[^;]+') do |
|
if arg2 == exclude:gsub('/', '\\') then |
|
return |
|
end |
|
end |
|
|
|
-- maintain the file |
|
local temp = {} |
|
local count = 0 |
|
temp[arg2] = {rank = 1, time = os.time()} |
|
for l in io.lines(datafile) do |
|
local _dir, _rank, _time = l:match('^(.+)%|(%d+)%|(%d+)$') |
|
if(_dir == arg2) then |
|
temp[_dir] = {rank = tonumber(_rank) + 1, time = os.time()} |
|
else |
|
temp[_dir] = {rank = tonumber(_rank), time = tonumber(_time)} |
|
end |
|
count = count + tonumber(_rank) |
|
end |
|
local f = io.open(datafile, 'w') |
|
for k, v in pairs(temp) do |
|
local stat = nyaos.stat(k) |
|
if stat and stat.directory then |
|
if(count > 6000) then |
|
f:write(k .. '|' .. v.rank * 0.99 .. '|' .. v.time .. '\n') |
|
else |
|
f:write(k .. '|' .. v.rank .. '|' .. v.time .. '\n') |
|
end |
|
end |
|
end |
|
f:close() |
|
|
|
elseif arg[1] == '--complete' then |
|
-- tab completion |
|
local ret = {} |
|
for l in io.lines(datafile) do |
|
local _dir, _rank, _time = l:match('^(.+)%|(%d+)%|(%d+)$') |
|
local stat = nyaos.stat(_dir) |
|
if stat and stat.directory then |
|
if _dir:lower():match((arg2 or ''):lower()) then |
|
table.insert(ret, {_dir, (_dir:match('[^/\\]+$') or '') .. '\\'}) |
|
end |
|
end |
|
end |
|
return ret |
|
|
|
elseif arg[1] == '--shortest-match' then |
|
-- shortest match |
|
local arg2 = arg[2]:gsub('/', '\\') |
|
local base, opt = arg2:match('^(.-)([^/\\]*)$') |
|
if opt:len() == 0 then |
|
return arg2 |
|
end |
|
local m = {} |
|
for l in io.lines(datafile) do |
|
local _dir, _rank, _time = l:match('^(.+)%|(%d+)%|(%d+)$') |
|
local stat = nyaos.stat(_dir) |
|
if stat and stat.directory then |
|
if _dir:lower() == arg2:lower() then |
|
return _dir |
|
elseif _dir:lower():match(base:lower()) and _dir:lower():match(opt:lower()) then |
|
table.insert(m, _dir) |
|
end |
|
end |
|
end |
|
if #m == 0 then |
|
return nil |
|
end |
|
local ret = m[1] |
|
for i = 1, #m do |
|
ret = _shortest_match(ret, m[i]) |
|
end |
|
return ret |
|
|
|
else |
|
-- list/go |
|
local list, typ |
|
local last = '' |
|
local fnd = {} |
|
local pwd = nyaos.eval('pwd') |
|
for i = 1, #arg do |
|
if arg[i] == '--' then |
|
for j = i, #arg do |
|
table.insert(fnd, arg[j]) |
|
end |
|
i = #arg |
|
elseif arg[i]:match('^-.') then |
|
if arg[i]:match('c') then |
|
table.insert(fnd, pwd) |
|
table.insert(fnd, arg[i]) |
|
elseif arg[i]:match('h') then |
|
return (os.getenv('_Z_CMD') or 'z') .. ' [-chlrt] arg' |
|
elseif arg[i]:match('l') then |
|
list = 1 |
|
elseif arg[i]:match('r') then |
|
typ = 'rank' |
|
elseif arg[i]:match('-t') then |
|
typ = 'recent' |
|
end |
|
else |
|
table.insert(fnd, arg[i]) |
|
end |
|
last = arg[i] |
|
end |
|
if #fnd == 1 and fnd[1] == pwd then |
|
list = 1 |
|
end |
|
|
|
-- if we hit enter on a completion just go there |
|
local dir = last:gsub('\034', ''):gsub('\\$', ''):gsub('/', '\\') |
|
local stat = nyaos.stat(dir) |
|
if stat and stat.directory then |
|
nyaos.exec('cd ' .. last) |
|
end |
|
|
|
function frecent(rank, time) |
|
local dx = os.time() - time |
|
if dx < 3600 then |
|
return rank * 4 |
|
elseif dx < 86400 then |
|
return rank * 2 |
|
elseif dx < 604800 then |
|
return rank / 2 |
|
end |
|
return rank / 4 |
|
end |
|
function output(files, toopen, override) |
|
if list then |
|
local r = {} |
|
for k, v in pairs(files) do |
|
if v and v ~= 0 then |
|
table.insert(r, {v, k}) |
|
end |
|
end |
|
table.sort(r, function(a, b) return (a[1] < b[1]) end) |
|
if override then |
|
print(string.format('%-10s %s', 'common:', override)) |
|
end |
|
for i = 1, #r do |
|
print(string.format('%-10s %s', r[i][1], r[i][2])) |
|
end |
|
else |
|
if override then |
|
return override |
|
else |
|
return toopen |
|
end |
|
end |
|
end |
|
function common(matches) |
|
-- shortest match |
|
local ret = nil |
|
for k, v in pairs(matches) do |
|
if ret then |
|
ret = _shortest_match(ret, k) |
|
else |
|
ret = k |
|
end |
|
end |
|
for k, v in pairs(matches) do |
|
if k == ret then |
|
return ret |
|
end |
|
end |
|
end |
|
|
|
local cd, cx, ncx |
|
local wcase = {} |
|
local nocase = {} |
|
local oldf = -9999999999 |
|
local noldf = -9999999999 |
|
for l in io.lines(datafile) do |
|
local _dir, _rank, _time = l:match('^(.+)%|(%d+)%|(%d+)$') |
|
local stat = nyaos.stat(_dir) |
|
if stat and stat.directory then |
|
local f |
|
if typ == 'rank' then |
|
f = tonumber(_rank) |
|
elseif typ == 'recent' then |
|
f = tonumber(_time) - os.time() |
|
else |
|
f = frecent(tonumber(_rank), tonumber(_time)) |
|
end |
|
wcase[_dir] = f |
|
nocase[_dir] = f |
|
for i = 1, #fnd do |
|
local fndi = fnd[i]:gsub('/', '\\') |
|
if not _dir:match(fndi) then |
|
wcase[_dir] = nil |
|
end |
|
if not _dir:lower():match(fndi:lower()) then |
|
nocase[_dir] = nil |
|
end |
|
end |
|
if wcase[_dir] and wcase[_dir] > oldf then |
|
cx = _dir |
|
oldf = wcase[_dir] |
|
elseif nocase[_dir] and nocase[_dir] > noldf then |
|
ncx = _dir |
|
noldf = nocase[_dir] |
|
end |
|
end |
|
end |
|
if cx then |
|
cd = output(wcase, cx, common(wcase)) |
|
else |
|
cd = output(nocase, ncx, common(nocase)) |
|
end |
|
if cd then |
|
nyaos.exec('cd ' .. cd) |
|
end |
|
end |
|
end |
|
function nyaos.command._z(...) |
|
local r = _z(...) |
|
local arg = {...} |
|
if r then |
|
if arg[1] == '--complete' then |
|
for i = 1, #r do |
|
print(r[i][1]) |
|
end |
|
else |
|
print(r) |
|
end |
|
end |
|
end |
|
|
|
nyaos.alias[os.getenv('_Z_CMD') or 'z'] = '_z' |
|
|
|
function nyaos.keyhook._z(t) |
|
function _is_tab() |
|
for k in string.gmatch(os.getenv('_Z_TAB') or 'TAB;CTRL_I', '[^;]+') do |
|
if t.key == nyaos.key[k] then |
|
return true |
|
end |
|
end |
|
return false |
|
end |
|
|
|
local cl = t.text:match('[;|]') and t.text:match('[^;|]+$') or t.text |
|
local cmd = (cl:match('^%s*\034([^\034]+)\034') or cl:match('^%s*([^%s]+)') or ''):match('[^/\\]+$') |
|
if cmd == (os.getenv('_Z_CMD') or 'z') then |
|
local arg = cl:match(cmd .. '%s+(.*)') |
|
if arg and _is_tab() then |
|
local s = _z('--shortest-match', arg:gsub('/', '\\')) |
|
if s and s ~= arg then |
|
local t = {} |
|
for i = 1, #arg do |
|
table.insert(t, nyaos.key[os.getenv('_Z_BACKSPACE') or 'BACKSPACE']) |
|
end |
|
table.insert(t, s) |
|
return t |
|
end |
|
end |
|
end |
|
end |
|
|
|
-- nyaos tab completion. avoid clobbering other nyaos.complete. |
|
if not os.getenv('_Z_NO_COMPLETE') then |
|
if nyaos_complete_by then |
|
nyaos_complete_by[os.getenv('_Z_CMD') or 'z'] = function(basestring, pos, misc) |
|
return _z('--complete', basestring) |
|
end |
|
else |
|
function nyaos.complete(basestring, pos, misc) |
|
local cl = misc.text:match('[;|]') and misc.text:match('[^;|]+$') or misc.text |
|
local cmd = (cl:match('^%s*\034([^\034]+)\034') or cl:match('^%s*([^%s]+)') or ''):match('[^/\\]+$') |
|
if cmd == (os.getenv('_Z_CMD') or 'z') then |
|
return _z('--complete', basestring) |
|
end |
|
return nyaos.default_complete(basestring, pos) |
|
end |
|
end |
|
end |
|
|
|
-- nyaos populate directory list. avoid clobbering other nyaos.prompt. |
|
if not os.getenv('_Z_NO_PROMPT_COMMAND') then |
|
function nyaos.prompt(p) |
|
_z('--add', nyaos.eval('pwd')) |
|
end |
|
end |
|
|
|
-- vim: ft=lua |