zoxide icon indicating copy to clipboard operation
zoxide copied to clipboard

Support for cmd.exe

Open fabioz opened this issue 4 years ago • 16 comments

Usually I use cmder as a shell which mostly wraps cmd in Windows -- and I'm really happy with it -- i.e.: https://cmder.net/ (https://github.com/cmderdev/cmder).

Is it possible to use zoxide in it? I only found instructions for power shell in windows, not really cmd, but maybe I missed something...

fabioz avatar Jun 01 '21 12:06 fabioz

Hey @fabioz, cmd.exe is not supported at the moment, you'd only be able to use cmder with the Bash and PowerShell modes. I don't know cmd.exe, so I can't be very helpful here -- but if you're proficient with it, it shouldn't take very long to implement:

  • z calls zoxide query
  • cd calls zoxide add
  • zoxide init <SHELL> is a useful reference for how this is implemented in other shells.

Unless this issue gains a lot of traction, I'd be hesitant to support it officially:

  • Microsoft is moving everything over to PowerShell as the new default
  • cmd.exe has been deprecated for years now

However, if you do end up implementing it on your own, do post it here -- I'm sure other cmd.exe users would find it helpful.

ajeetdsouza avatar Jun 01 '21 19:06 ajeetdsouza

I use cmd too and I would like support for zoxide. Powershell is terrible.

mwaitzman avatar Jun 02 '21 09:06 mwaitzman

@ajeetdsouza FWIW, cmd on its own isn't very configurable. Cmder uses a framework called Clink which adds Bash's Readline support to it and makes it highly extensible (there's a comprehensive documentation available here).

However, if z and cd are the only two commands/functions as you specified above, then they can be easily added through doskey aliases:

  • doskey z=zoxide query $*
  • doskey cd=zoxide add $1 $T cd /d $1

rashil2000 avatar Aug 05 '21 16:08 rashil2000

Yeah, is it possible to get clink support for this?

It's quite powerful and I think z.lua already does support it.

Lunchb0ne avatar Sep 11 '21 09:09 Lunchb0ne

Here's a lua script I wrote for cmd.exe with clink. Put this into a zoxide.lua file in clink's lua scripts location. For cmder you can put it in %cmder_root%\config\zoxide.lua.

I only did some manual testing but it should be able to support many of zoxide's features including:

  • zoxide init options --cmd, --hook, and --no-alias (using clink settings)
  • _ZO_ECHO environment variable
  • fzf integration via zi command
-- =============================================================================
--
-- Settings copied from 'zoxide init'. Run `clink set` to modify these options, e.g. `clink set zoxide.cmd f`
--

settings.add('zoxide.cmd', 'z', 'Changes the prefix of the aliases')
settings.add('zoxide.hook', { 'pwd', 'prompt', 'none' }, 'Changes when directory scores are incremented')
settings.add('zoxide.no_aliases', false, "Don't define aliases")

-- =============================================================================
--
-- Utility functions for zoxide.
--

-- Generate `cd` command
local function __zoxide_cd(dir)
  if os.getenv '_ZO_ECHO' == '1' then
    print(dir)
  end

  -- 'cd /d -' doesn't work for clink versions before v1.2.41 (https://github.com/chrisant996/clink/issues/191)
  -- lastest cmder release (v1.3.18) uses clink v1.1.45
  if dir == '-' and (clink.version_encoded or 0) < 10020042 then
    return 'cd -'
  end

  return 'cd /d ' .. dir
end

-- Run `zoxide query` and generate `cd` command from result
local function __zoxide_query(options, keywords)
  options = table.concat(options, ' ')
  keywords = table.concat(keywords, ' ')

  local file = io.popen('zoxide query ' .. options .. ' -- ' .. keywords)
  local result = file:read '*line'
  local ok = file:close()

  if ok then
    return __zoxide_cd(result)
  else
    return 'call' -- no-op that just sets %ERRORLEVEL% to 1
  end
end

-- Add directory to the database.
local function __zoxide_add(dir)
  os.execute('zoxide add -- "' .. dir .. '"')
end

-- =============================================================================
--
-- Hook configuration for zoxide.
--

local __zoxide_oldpwd
local __zoxide_prompt = clink.promptfilter()

function __zoxide_prompt:filter()
  local zoxide_hook = settings.get 'zoxide.hook'

  if zoxide_hook == 'none' then
    -- do nothing
    return
  elseif zoxide_hook == 'prompt' then
    -- run `zoxide add` on every prompt
    __zoxide_add(os.getcwd())
  elseif zoxide_hook == 'pwd' then
    -- run `zoxide add` when the working directory changes
    local cwd = os.getcwd()
    if __zoxide_oldpwd and __zoxide_oldpwd ~= cwd then
      __zoxide_add(cwd)
    end
    __zoxide_oldpwd = cwd
  end
end

-- =============================================================================
--
-- Define aliases.
--

-- 'z' alias
local function __zoxide_z(keywords)
  if #keywords == 0 then
    return __zoxide_cd(os.getenv 'USERPROFILE')
  elseif #keywords == 1 then
    local keyword = keywords[1]
    if keyword == '-' then
      return __zoxide_cd '-'
    elseif os.isdir(keyword) then
      return __zoxide_cd(keyword)
    end
  end

  local cwd = '"' .. os.getcwd() .. '"'
  return __zoxide_query({ '--exclude', cwd }, keywords)
end

-- 'zi' alias
local function __zoxide_zi(keywords)
  return __zoxide_query({ '--interactive' }, keywords)
end

-- =============================================================================
--
-- Clink input text filter.
--

local function onfilterinput(text)
  args = string.explode(text, ' ', '"')
  if #args == 0 then
    return
  end

  -- settings
  zoxide_cmd = settings.get 'zoxide.cmd'
  zoxide_no_aliases = settings.get 'zoxide.no_aliases'

  -- edge case:
  -- * zoxide command prefix is 'cd'
  -- * clink converted 'cd -' -> 'cd /d "some_directory"'
  local cd_regex = '^%s*cd%s+/d%s+"(.-)"%s*$'
  if zoxide_cmd == 'cd' and text:match(cd_regex) then
    if zoxide_no_aliases then
      -- clink handles it
      return
    else
      -- zoxide handles it
      return __zoxide_cd(text:gsub(cd_regex, '%1')), false
    end
  end

  local cmd = table.remove(args, 1)
  if cmd == '__zoxide_z' or (cmd == zoxide_cmd and not zoxide_no_aliases) then
    return __zoxide_z(args), false
  elseif cmd == '__zoxide_zi' or (cmd == zoxide_cmd .. 'i' and not zoxide_no_aliases) then
    return __zoxide_zi(args), false
  else
    return
  end
end

if clink.onfilterinput then
  clink.onfilterinput(onfilterinput)
else
  clink.onendedit(onfilterinput)
end

-- =============================================================================
--
-- To initalize zoxide, add this script to one of clink's lua script locations (e.g. zoxide.lua)
-- (see https://chrisant996.github.io/clink/clink.html#location-of-lua-scripts)

It can definitely be optimized some more, especially if the script was rendered using a template like the other shells supported by zoxide. @ajeetdsouza if you ever feel like you want to officially support cmd.exe, I would be happy to try to submit a PR for this.

shunsambongi avatar Jan 08 '22 09:01 shunsambongi

Wow, beautiful script.

@shunsambongi in the meanwhile you can also share this script on the Clink repo (maybe the author will mention it in Clink's docs) :)

rashil2000 avatar Jan 08 '22 10:01 rashil2000

Wow, beautiful script.

@shunsambongi in the meanwhile you can also share this script on the Clink repo (maybe the author will mention it in Clink's docs) :)

Yes, I think this would be really helpful too!

Lunchb0ne avatar Jan 08 '22 20:01 Lunchb0ne

@rashil2000 @Lunchb0ne I'm glad you like the script! I shared the script in clink's repo like you suggested. I also put the script in its own repo so people might find it easier. https://github.com/shunsambongi/clink-zoxide

shunsambongi avatar Jan 09 '22 01:01 shunsambongi

I have added your lua script but there is an issue not having the clink settings.

%CMDER_ROOT%\config\zoxide.lua:6: attempt to index global 'settings' (a nil value)

Are there additional steps using this script?

Edit: Never mind, my cmder installation was outdated. I thought the cmder updater would update cmder but it is only conemu.

hacki11 avatar Jan 23 '22 11:01 hacki11

Its already working.now

rashil2000 commented Aug 5, 2021

@ajeetdsouza FWIW, cmd on its own isn't very configurable. Cmder uses a framework called Clink which adds Bash's Readline support to it and makes it highly extensible (there's a comprehensive documentation available here).

However, if z and cd are the only two commands/functions as you specified above, then they can be easily added through doskey aliases:

doskey z=zoxide query $*
doskey cd=zoxide add $1 $T cd /d $1

for example X:\XJava\apps>z X:\apps\inpath\lang

as you can see the z shortcut is perfectly working.it suggest a path i used before but is totally at other place.

RESDXChgfore9hing avatar Dec 26 '22 11:12 RESDXChgfore9hing

maybe someone can just do a documentation pr and can close this issue.

RESDXChgfore9hing avatar Dec 26 '22 11:12 RESDXChgfore9hing

it however has this repeatedly echo currentdir when cd .. bug. X:\XJava>cd ..

X:\XJava> X:>

RESDXChgfore9hing avatar Dec 26 '22 12:12 RESDXChgfore9hing

maybe a small patch when cd .. will not call zoxide add or just simply make zoxide not respond to cd .. ??

RESDXChgfore9hing avatar Dec 26 '22 12:12 RESDXChgfore9hing

I'm interested in getting this running; I actually have a prototype that's working... almost.

  • z calls zoxide query
  • cd calls zoxide add

What path format do these commands expect? I can't use UNC as cmd.exe does not support it:

C:\Users\user>2>&1 chdir \\?\C:\Users
'\\?\C:\Users'
CMD does not support UNC paths as current directories.

mataha avatar May 10 '23 00:05 mataha

@mataha zoxide add takes in any kind of path and handles the normalization on its own. Most shells just use something like zoxide add "$PWD" or zoxide add "$(pwd -L)", but you could even use zoxide add . and it should work correctly.

zoxide query is just simple query strings, it does not take a path as an argument.

ajeetdsouza avatar May 10 '23 07:05 ajeetdsouza