├── README.md └── zoxide.lua /README.md: -------------------------------------------------------------------------------- 1 | # zoxide support for clink 2 | 3 | Adds `z` and `zi` aliases for using zoxide within clink. 4 | 5 | ## Installation 6 | 7 | Add the `zoxide.lua` script to your clink [lua scripts location](https://chrisant996.github.io/clink/clink.html#location-of-lua-scripts). 8 | 9 | ## Configuration 10 | 11 | This script uses [clink settings](https://chrisant996.github.io/clink/clink.html#clink-settings) for the options that are usually supplied via command line flags to `zoxide init`. Run the `clink set` command to set the options: 12 | 13 | - `zoxide.cmd` maps to the `zoxide init --cmd` option 14 | - `zoxide.hook` maps to the `zoxide init --hook` option 15 | - `zoxide.no_aliases` maps to the `zoxide init --no-aliases` option 16 | 17 | -------------------------------------------------------------------------------- /zoxide.lua: -------------------------------------------------------------------------------- 1 | -- ============================================================================= 2 | -- 3 | -- Settings copied from 'zoxide init'. Run `clink set` to modify these options, e.g. `clink set zoxide.cmd f` 4 | -- 5 | 6 | settings.add('zoxide.cmd', 'z', 'Changes the prefix of the aliases') 7 | settings.add('zoxide.hook', { 'pwd', 'prompt', 'none' }, 'Changes when directory scores are incremented') 8 | settings.add('zoxide.no_aliases', false, "Don't define aliases") 9 | settings.add('zoxide.usepromptfilter', false, "Use clink promptfilter to hook even if onbeginedit is supported") 10 | settings.add('zoxide.promptfilter_prio', -50, "Changes the priority of the promptfilter hook (only if usepromptfilter is true)") 11 | 12 | -- ============================================================================= 13 | -- 14 | -- Utility functions for zoxide. 15 | -- 16 | 17 | -- Generate `cd` command 18 | local function __zoxide_cd(dir) 19 | if os.getenv '_ZO_ECHO' == '1' then 20 | print(dir) 21 | end 22 | 23 | -- 'cd /d -' doesn't work for clink versions before v1.2.41 (https://github.com/chrisant996/clink/issues/191) 24 | -- lastest cmder release (v1.3.18) uses clink v1.1.45 25 | if dir == '-' and (clink.version_encoded or 0) < 10020042 then 26 | return ' cd -' 27 | end 28 | 29 | return ' cd /d ' .. dir 30 | end 31 | 32 | -- Run `zoxide query` and generate `cd` command from result 33 | local function __zoxide_query(options, keywords) 34 | options = table.concat(options, ' ') 35 | keywords = table.concat(keywords, ' ') 36 | 37 | local file = io.popen('zoxide query ' .. options .. ' -- ' .. keywords) 38 | local result = file:read '*line' 39 | local ok = file:close() 40 | 41 | if ok then 42 | return __zoxide_cd(result) 43 | else 44 | return 'call' -- no-op that just sets %ERRORLEVEL% to 1 45 | end 46 | end 47 | 48 | -- Add directory to the database. 49 | local function __zoxide_add(dir) 50 | os.execute('zoxide add -- "' .. dir:gsub('^(.-)\\*$', '%1') .. '"') 51 | end 52 | 53 | -- ============================================================================= 54 | -- 55 | -- Hook configuration for zoxide. 56 | -- 57 | local __usepromptfilter = settings.get 'zoxide.usepromptfilter' 58 | 59 | if not clink.onbeginedit then 60 | __usepromptfilter = true 61 | end 62 | 63 | local __zoxide_oldpwd 64 | function __zoxide_hook() 65 | local zoxide_hook = settings.get 'zoxide.hook' 66 | 67 | if zoxide_hook == 'none' then 68 | -- do nothing 69 | return 70 | elseif zoxide_hook == 'prompt' then 71 | -- run `zoxide add` on every prompt 72 | __zoxide_add(os.getcwd()) 73 | elseif zoxide_hook == 'pwd' then 74 | -- run `zoxide add` when the working directory changes 75 | local cwd = os.getcwd() 76 | if __zoxide_oldpwd and __zoxide_oldpwd ~= cwd then 77 | __zoxide_add(cwd) 78 | end 79 | __zoxide_oldpwd = cwd 80 | end 81 | end 82 | 83 | if __usepromptfilter then 84 | local __promptfilter_prio = settings.get 'zoxide.promptfilter_prio' 85 | local __zoxide_prompt = clink.promptfilter(__promptfilter_prio) 86 | 87 | function __zoxide_prompt:filter() 88 | __zoxide_hook() 89 | end 90 | else 91 | clink.onbeginedit(__zoxide_hook) 92 | end 93 | 94 | -- ============================================================================= 95 | -- 96 | -- Define aliases. 97 | -- 98 | 99 | -- remove double quotes 100 | function unquote(s) 101 | local unquoted = string.match(s, '^"(.*)"$') 102 | if unquoted then 103 | return unquoted 104 | end 105 | return s 106 | end 107 | 108 | -- 'z' alias 109 | local function __zoxide_z(keywords) 110 | if #keywords == 0 then 111 | -- NOTE: `os.getenv("HOME")` returns HOME or HOMEDRIVE+HOMEPATH 112 | -- or USERPROFILE. 113 | return __zoxide_cd(os.getenv('HOME')) 114 | elseif #keywords == 1 then 115 | local keyword = keywords[1] 116 | if keyword == '-' then 117 | return __zoxide_cd '-' 118 | else 119 | local path = os.getfullpathname(unquote(keyword)) 120 | if path and os.isdir(path) then 121 | return __zoxide_cd(path) 122 | end 123 | end 124 | end 125 | 126 | local cwd = '"' .. os.getcwd() .. '"' 127 | return __zoxide_query({ '--exclude', cwd }, keywords) 128 | end 129 | 130 | -- 'zi' alias 131 | local function __zoxide_zi(keywords) 132 | return __zoxide_query({ '--interactive' }, keywords) 133 | end 134 | 135 | -- ============================================================================= 136 | -- 137 | -- Clink input text filter. 138 | -- 139 | 140 | local function onfilterinput(text) 141 | args = string.explode(text, ' ', '"') 142 | if #args == 0 then 143 | return 144 | end 145 | 146 | -- settings 147 | zoxide_cmd = settings.get 'zoxide.cmd' 148 | zoxide_no_aliases = settings.get 'zoxide.no_aliases' 149 | 150 | -- edge case: 151 | -- * zoxide command prefix is 'cd' 152 | -- * clink converted 'cd -' -> 'cd /d "some_directory"' 153 | local cd_regex = '^%s*cd%s+/d%s+"(.-)"%s*$' 154 | if zoxide_cmd == 'cd' and text:match(cd_regex) then 155 | if zoxide_no_aliases then 156 | -- clink handles it 157 | return 158 | else 159 | -- zoxide handles it 160 | return __zoxide_cd(text:gsub(cd_regex, '%1')), false 161 | end 162 | end 163 | 164 | local cmd = table.remove(args, 1) 165 | if cmd == '__zoxide_z' or (cmd == zoxide_cmd and not zoxide_no_aliases) then 166 | return __zoxide_z(args), false 167 | elseif cmd == '__zoxide_zi' or (cmd == zoxide_cmd .. 'i' and not zoxide_no_aliases) then 168 | return __zoxide_zi(args), false 169 | else 170 | return 171 | end 172 | end 173 | 174 | if clink.onfilterinput then 175 | clink.onfilterinput(onfilterinput) 176 | else 177 | clink.onendedit(onfilterinput) 178 | end 179 | 180 | -- ============================================================================= 181 | -- 182 | -- Clink command color. 183 | -- 184 | 185 | local cl = clink.classifier(50) 186 | 187 | function cl:classify(commands) 188 | if not commands[1] then 189 | return 190 | end 191 | 192 | local color = settings.get('color.doskey') 193 | if not color or color == '' then 194 | return 195 | end 196 | 197 | local zoxide_cmd = settings.get('zoxide.cmd') 198 | 199 | local line_state = commands[1].line_state 200 | local classifications = commands[1].classifications 201 | local line = line_state:getline() 202 | 203 | if line:match("^%s*"..zoxide_cmd.." ") then 204 | -- cannot combine match and find - we need to know that zoxide_cmd is the first thing 205 | local start = line:find(zoxide_cmd) 206 | classifications:applycolor(start, #zoxide_cmd, color, true--[[overwrite]]) 207 | elseif line:match("^%s*"..zoxide_cmd.."i ") then 208 | local start = line:find(zoxide_cmd) 209 | -- add one to the span to apply color to, for the extra 'i' 210 | classifications:applycolor(start, #zoxide_cmd + 1, color, true--[[overwrite]]) 211 | end 212 | end 213 | 214 | -- ============================================================================= 215 | -- 216 | -- To initalize zoxide, add this script to one of clink's lua script locations (e.g. zoxide.lua) 217 | -- (see https://chrisant996.github.io/clink/clink.html#location-of-lua-scripts) 218 | --------------------------------------------------------------------------------