wezterm icon indicating copy to clipboard operation
wezterm copied to clipboard

Executable File Existence Check

Open frostbyte-ninja opened this issue 1 year ago • 2 comments

Hi,

I am encountering the same issue as described here. The suggested solution there does not appear to be optimal. To determine the availability of executables in the path, I implemented the following function:

local function is_executable_in_path(executable)
  local command
  if wezterm.target_triple:find("linux") ~= nil then
    command = "command -v " .. executable
  elseif wezterm.target_triple:find("windows") ~= nil then
    command = "where " .. executable
  else
    return false
  end
  return os.execute(command)
end

While this performs as expected on Linux, on Windows each call results in the spawning of a shell window. This not only disrupts the visual flow but also significantly increases startup time when multiple checks are performed.

My goal is to determine which shells are available on the system to dynamically create launch_menu entries.

I would greatly appreciate a more efficient approach to address this issue.

Thank you.

frostbyte-ninja avatar Aug 14 '24 17:08 frostbyte-ninja

If you just want to know which shells are available on the system, you can check /etc/shells for Linux/Mac. There doesn't seem to be a good way to do this on Windows.

kenchou avatar Aug 21 '24 03:08 kenchou

Ran into similar issue where I wanted to use fish if it exists.

I have used homebrew so it install fish at /opt/homebrew/bin/fish which might not be in the path.

So I have added the following to my western config.

local function file_exists(path)
    local f = io.open(path, "r")
    if f~=nil then io.close(f) return true else return false end
end

if file_exists("/opt/homebrew/bin/fish") then
    config.default_prog = { '/opt/homebrew/bin/fish', '-l' }
else
    config.default_prog = { '/bin/bash', '-l' }
end

If I want to add it to launch menu I would also need an if else here.

{ label = "fish", args = {"/opt/homebrew/bin/fish", "-l"}

It almost seems like there should also be an easy way to add /opt/homebrew/bin to the path and have a function to see if there executable is available so one can set the args as just {"fish", "-l"}

prabirshrestha avatar Aug 21 '24 04:08 prabirshrestha

Another option for this is use wezterm.glob to check if a specific path exists, here I'm in the debug overlay on a system that doesn't have homebrew or fish, but does have zsh at the paths I'm checking:

Debug Overlay
wezterm version: 20240812-215703-30345b36 x86_64-unknown-linux-gnu
Enter lua statements or expressions and hit Enter.
Press ESC or CTRL-D to exit
> wezterm.glob('/opt/homebrew/bin/fish')
[]
> wezterm.glob('/bin/zsh')
[
    "/bin/zsh",
]
>

Glob expressions can include multiple candidates, so you can check for multiple paths at the same time. Here I'm checking for zsh, bash, fish and csh:

> wezterm.glob('/bin/{zsh,bash,fish,csh}')
[
    "/bin/bash",
    "/bin/zsh",
]

wez avatar Sep 15 '24 15:09 wez

This was the first search result, and after finding it and going off on my own I got some results, so I'll post my findings here:

if wezterm.target_triple == 'x86_64-pc-windows-msvc' then

  local function is_executable_in_path(executable)
    return wezterm.run_child_process { 'where.exe', '/Q', executable }
  end

  local pwsh = is_executable_in_path 'pwsh.exe'
  local powershell = is_executable_in_path 'powershell.exe'
  local git = is_executable_in_path 'git.exe'
  local elvish = is_executable_in_path 'elvish.exe'
  local nu = is_executable_in_path 'nu.exe'

  -- Use powershell to query the registry for the Git for Windows install path
  local bash_path = ''
  if git and ( pwsh or powershell ) then
    local shell = pwsh and 'pwsh.exe' or 'powershell.exe'
    local git_registry, git_path, stderr = wezterm.run_child_process {
      shell,
      '-Command',
      [[(Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\GitForWindows).InstallPath]],
    }
    if git_registry then
      for _, line in ipairs(wezterm.split_by_newlines(git_path)) do
        bash_path = bash_path .. line
      end
      bash_path = bash_path .. [[\bin\bash.exe]]
    end
  end

  -- Check for pwsh
  if pwsh then
    table.insert(launch_menu, {
      label = 'PowerShell',
      args = { 'pwsh.exe', '-NoLogo' },
    })
    config.default_prog = { 'pwsh.exe', '-NoLogo' }

    if git and bash_path ~= '' then
      table.insert(launch_menu, {
        label = 'Git Bash',
        args = { bash_path, '-i', '-l' },
      })
    end

    table.insert(launch_menu, {
      label = 'Command Prompt',
      args = { os.getenv 'COMSPEC', '/k' },
    })

  -- No pwsh but we have Windows PowerShell
  elseif powershell then
    table.insert(launch_menu, {
      label = 'Windows PowerShell',
      args = { 'powershell.exe', '-NoLogo' },
    })
    config.default_prog = { 'powershell.exe', '-NoLogo' }

    if git and bash_path ~= '' then
      table.insert(launch_menu, {
        label = 'Git Bash',
        args = { bash_path, '-i', '-l' },
      })
    end

    table.insert(launch_menu, {
      label = 'Command Prompt',
      args = { os.getenv 'COMSPEC', '/k' },
    })

  -- No powershell of any kind, just cmd.exe
  else
    table.insert(launch_menu, {
      label = 'Command Prompt',
      args = { os.getenv 'COMSPEC', '/k' },
    })
    config.default_prog = {  os.getenv 'COMSPEC', '/k' }
  end

  -- Check for elvish
  if elvish then
    table.insert(launch_menu, {
      label = 'Elvish',
      args = { 'elvish.exe' },
    })
  end

  -- Check for NuShell
  if nu then
    table.insert(launch_menu, {
      label = 'NuShell',
      args = { 'nu.exe' },
    })
  end
end

Some details

  1. if wezterm.run_child_process { 'where.exe', '/Q', 'thing.exe' } then works just fine as a check on windows. On linux/osx you'll want to use wezterm.glob() as there is no portable solution, they all vary by shell, since which/whereis vary by distribution and can't be relied upon, and the built-ins that won't screw you over occasionally vary by shell between bash/csh/dash/fish/sh/zsh/tcsh, and sometimes by version.
    • Use where.exe, because you might otherwise hit the powershell built-in where which functions slightly differently; where.exe is unchanged in functionality since the DOS era, the /Q flag makes it only return found/not found.
  2. $COMSPEC and %SystemRoot%\System32\cmd.exe are the canonical ways to get a DOS command line. os.getenv() will fail if the variable named doesn't exist, but if your windows install doesn't have $COMSPEC you have issues.
  3. The call to pwsh.exe -Command to query the registry is extremely slow - it adds over a second to startup time - even though it is ~instantaneous in a terminal.
    • No idea why, the only LUA experience I've had before is neovim configs, and that was mostly setting options. I could well be doing something atrocious.
      • The only other way to get a registry value from commandline is reg.exe query which does not output a machine-readable format, and the way it prints varies by version and thus is very hard to parse reliably:
      ❯ reg.exe query "HKEY_LOCAL_MACHINE\Software\GitForWindows" /v "InstallPath"
      
      HKEY_LOCAL_MACHINE\Software\GitForWindows
          InstallPath    REG_SZ    C:\Program Files\Git
      
    • There doesn't seem to be any mature way of interacting with the registry via Lua, except for in Lua^rt.
  • I'm doing the split-by-newline-and-rejoin dance because the values returned by the powershell command are newline-terminated and this is more robust than doing it manually.
  • You could alternatively run where.exe git.exe (no /Q flag) and strip the cmd\git.exe at the end of the second (fully qualified path) return value. This will be much faster but also far more likely to break.

wez, if you're getting the WSL entries via registry, maybe you can expose the query method? That or auto-populate a Git-Bash entry, I imagine the intersection of "Windows users who use WezTerm" and "Windows users who use Git" is significant.

Taverius avatar Dec 10 '24 23:12 Taverius