This is an update of a previous post that was specific to SDKMAN’s predecessor, GVM. The code and details have been updated to work with SDKMAN.

The Fish Shell is awesome but it doesn’t have a Posix-compatible shell language like Bash or ZSH. This is a problem when you’re trying to use third-party tools that expect a Posix-compatible shell. Tools like SDKMAN, the Software Development Kit Manager.

SDKMAN, like it’s inspiration RVM, lets a developer switch between versions of the language toolchain. This is important because modern languages are moving targets and building an application against one version of the toolchain isn’t guaranteed to run against later versions.

SDKMAN, in particular, wires itself into the Bash and ZSH shells. The best approach to getting SDKMAN to work with Fish is to rewrite it for Fish. But the developers for GVM don’t seem inclined to do that. So we have to cheat.

There’s an existing bit of code in Oh My Fish that’s supposed to make GVM (the predecessor to SDKMAN) work with Fish but that code doesn’t actually work. So I put together an alternative that works cleanly with SDKMAN. The first bit of code uses a Fish function to invoke gvm. It goes in the “~/.config/fish/functions” directory and must be named “~/.config/fish/functions/sdk.fish”.

function sdk --description 'Software Development Kit Manager'
  set after_env (mktemp -t env)
  set path_env (mktemp -t env)

    bash -c "source ~/.sdkman/bin/sdkman-init.sh && sdk $argv && printenv > $after_env"

    # remove any pre-existing .sdkman paths
    for elem in $PATH
      switch $elem
        case '*/.sdkman/*'
          # ignore
        case '*'
          echo "$elem" >> $path_env
      end
    end

    for env in (cat $after_env)
      set env_name (echo $env | sed s/=.\*//)
      set env_value (echo $env | sed s/.\*=//)
      switch $env_name
        case 'PATH'
          for elem in (echo $env_value | tr ':' '\n')
            switch $elem
              case '*/.sdkman/*'
                echo "$elem" >> $path_env
            end
          end
        case '*'
          switch $env_value
            case '*/.sdkman/*'
              eval set -g $env_name $env_value > /dev/null
          end
      end
    end
    set -gx PATH (cat $path_env) ^ /dev/null

    rm -f $after_env
    rm -f $path_env
end

This code works by running the SDKMAN ‘init’ command and then the actual ‘sdk’ command in a Bash shell. It compares the environment before and after running those commands and applies the new or changed environment variables against the Fish environment. It does the same for the PATH environment variable.

The last bit of code is optional, but, ironically, is also the most complicated. It provides command-line completion for the ‘sdk’ command. It goes in a new file named “~/.config/fish/completions/sdk.fish”. The completion script parses the installed versions and will provide version number completion when using the ‘gvm’ command.

# sdkman autocompletion

function __fish_sdkman_no_command --description 'Test if sdkman has yet to be given the main command'
  set cmd (commandline -opc)
  if [ (count $cmd) -eq 1 ]
    return 0
  end
  return 1
end

function __fish_sdkman_using_command
  set cmd (commandline -opc)
  if [ (count $cmd) -eq 2 ]
    if [ $argv[1] = $cmd[2] ]
      return 0
    end
  end
  return 1
end

function __fish_sdkman_using_subcommand
  set cmd (commandline -opc)
  set cmd_main $argv[1]
  set cmd_sub $argv[2]

  if [ (count $cmd) -gt 2 ]
    if [ $cmd_main = $cmd[2] ]; and [ $cmd_sub = $cmd[3] ]
      return 0
    end
  end
  return 1
end

function __fish_sdkman_specifying_candidate
  set cmd (commandline -opc)

  if [ (count $cmd) -gt 2 ]
    if [ $argv[1] = $cmd[2] ]
      return 0
    end
  end
  return 1
end

function __fish_sdkman_candidates
  cat ~/.sdkman/var/candidates | tr ',' '\n'
end

function __fish_sdkman_installed_versions
  set cmd (commandline -opc)
  ls -v1 ~/.sdkman/$cmd[3] | grep -v current
end

# install
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'install' -d 'Install new version'
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'i' -d 'Install new version'
complete -c sdk -f -n '__fish_sdkman_using_command install' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_using_command i' -a "(__fish_sdkman_candidates)"  

# uninstall
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'uninstall' -d 'Uninstall version'
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'rm' -d 'Uninstall version'
complete -c sdk -f -n '__fish_sdkman_using_command uninstall' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_using_command rm' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_specifying_candidate uninstall' -a "(__fish_sdkman_installed_versions)" 
complete -c sdk -f -n '__fish_sdkman_specifying_candidate rm' -a "(__fish_sdkman_installed_versions)" 

# list
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'list' -d 'List versions'
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'ls' -d 'List versions'
complete -c sdk -f -n '__fish_sdkman_using_command list' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_using_command ls' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_specifying_candidate list' -a "(__fish_sdkman_installed_versions)" 
complete -c sdk -f -n '__fish_sdkman_specifying_candidate ls' -a "(__fish_sdkman_installed_versions)" 

# use
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'use' -d 'Use specific version'
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'u' -d 'Use specific version'
complete -c sdk -f -n '__fish_sdkman_using_command use' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_using_command u' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_specifying_candidate use' -a "(__fish_sdkman_installed_versions)" 
complete -c sdk -f -n '__fish_sdkman_specifying_candidate u' -a "(__fish_sdkman_installed_versions)" 

# default
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'default' -d 'Set default version'
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'd' -d 'Set default version'
complete -c sdk -f -n '__fish_sdkman_using_command default' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_using_command d' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_specifying_candidate default' -a "(__fish_sdkman_installed_versions)" 
complete -c sdk -f -n '__fish_sdkman_specifying_candidate d' -a "(__fish_sdkman_installed_versions)" 

# current
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'current' -d 'Display current version'
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'c' -d 'Display current version'
complete -c sdk -f -n '__fish_sdkman_using_command current' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_using_command c' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_specifying_candidate current' -a "(__fish_sdkman_installed_versions)" 
complete -c sdk -f -n '__fish_sdkman_specifying_candidate c' -a "(__fish_sdkman_installed_versions)" 

# outdated
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'outdated' -d 'List outdated versions'
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'o' -d 'List outdated versions'
complete -c sdk -f -n '__fish_sdkman_using_command outdated' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_using_command o' -a "(__fish_sdkman_candidates)"  
complete -c sdk -f -n '__fish_sdkman_specifying_candidate outdated' -a "(__fish_sdkman_installed_versions)" 
complete -c sdk -f -n '__fish_sdkman_specifying_candidate o' -a "(__fish_sdkman_installed_versions)" 

# version
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'version' -d 'Display version'
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'v' -d 'Display version'
complete -c sdk -f -n '__fish_sdkman_using_command version' 
complete -c sdk -f -n '__fish_sdkman_using_command v' 

# broadcast
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'broadcast' -d 'Display broadcast message'
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'b' -d 'Display broadcast message'
complete -c sdk -f -n '__fish_sdkman_using_command broadcast' 
complete -c sdk -f -n '__fish_sdkman_using_command b' 

# help
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'help' -d 'Display help message'
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'h' -d 'Display help message'
complete -c sdk -f -n '__fish_sdkman_using_command help' 
complete -c sdk -f -n '__fish_sdkman_using_command h' 

# offline
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'offline' -d 'Set offline status'
complete -c sdk -f -n '__fish_sdkman_using_command offline' -a 'enable disable'

# selfupdate
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'selfupdate' -d 'Update sdkman'
complete -c sdk -f -n '__fish_sdkman_using_command selfupdate' -a 'force'

# flush
complete -c sdk -f -n '__fish_sdkman_no_command' -a 'flush' -d 'Clear out cache'
complete -c sdk -f -n '__fish_sdkman_using_command flush' -a 'candidates broadcast archives temp'