[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non: push\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v3\n      - name: Install Fish\n        run: |\n          sudo apt-add-repository -yn ppa:fish-shell/release-3\n          sudo apt-get update\n          sudo apt-get install -y fish\n\n      - name: Install Tools\n        run: |\n          source $GITHUB_WORKSPACE/functions/fisher.fish\n          fisher install $GITHUB_WORKSPACE jorgebucaran/fishtape\n          fishtape tests/*.fish\n        shell: fish {0}\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright © Jorge Bucaran <<https://jorgebucaran.com>>\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "README.md",
    "content": "# Fisher\n\n> A plugin manager for [Fish](https://fishshell.com)—your friendly interactive shell. [Snag fresh plugins!](https://github.com/jorgebucaran/awsm.fish#readme)\n\nTake control of functions, completions, bindings, and snippets from the command line. Unleash your shell's true potential, perfect your prompt, and craft repeatable configurations across different systems effortlessly. Fisher's zero impact on shell startup keeps your shell zippy and responsive. No gimmicks, just smooth sailing!\n\n- Fisher is 100% pure-Fish, making it easy to contribute or modify\n- Scorching fast concurrent plugin downloads that'll make you question reality\n- Zero configuration needed—we're not kidding!\n- Oh My Fish! plugins supported too\n\n> #### ☝️ [Upgrading from Fisher `3.x` or older? Strap in and read this!](https://github.com/jorgebucaran/fisher/issues/652)\n\n## Installation\n\n```console\ncurl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher\n```\n\n## Quickstart\n\nFisher lets you install, update, and remove plugins like a boss. Revel in Fish's [tab completion](https://fishshell.com/docs/current/index.html#completion) and rich syntax highlighting while you're at it.\n\n### Installing plugins\n\nTo install plugins, use the `install` command and point it to the GitHub repository.\n\n```console\nfisher install jorgebucaran/nvm.fish\n```\n\n> Wanna install from GitLab? No problemo—just prepend `gitlab.com/` to the plugin path.\n\nYou can also snag a specific version of a plugin by adding an `@` symbol after the plugin name, followed by a tag, branch, or [commit](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefcommit-ishacommit-ishalsocommittish).\n\n```console\nfisher install IlanCosman/tide@v6\n```\n\nAnd hey, you can install plugins from a local directory too!\n\n```console\nfisher install ~/path/to/plugin\n```\n\n> Heads up! Fisher expands plugins into your Fish configuration directory by default, overwriting existing files. If that's not your jam, set `$fisher_path` to your preferred location and put it in your function path ([#640](https://github.com/jorgebucaran/fisher/issues/640)).\n\n### Listing plugins\n\nUse the `list` command to see all your shiny installed plugins.\n\n```console\n$ fisher list\njorgebucaran/fisher\nilancosman/tide@v5\njorgebucaran/nvm.fish\n/home/jb/path/to/plugin\n```\n\nThe `list` command also plays nice with regular expressions for filtering the output.\n\n```console\n$ fisher list \\^/\n/home/jb/path/to/plugin\n```\n\n### Updating plugins\n\n`update` command to the rescue! It updates one or more plugins to their latest and greatest version.\n\n```console\nfisher update jorgebucaran/fisher\n```\n\n> Just type `fisher update` to update everything in one fell swoop.\n\n### Removing plugins\n\nSay goodbye to installed plugins with the `remove` command.\n\n```console\nfisher remove jorgebucaran/nvm.fish\n```\n\nFeeling destructive? Wipe out everything, including Fisher itself.\n\n```console\nfisher list | fisher remove\n```\n\n## Using your `fish_plugins` file\n\nWhenever you install or remove a plugin from the command line, Fisher jots down all the installed plugins in `$__fish_config_dir/fish_plugins`. Add this file to your dotfiles or version control to easily share your configuration across different systems.\n\nYou can also edit this file and run `fisher update` to commit changes like a pro:\n\n```console\n$EDITOR $__fish_config_dir/fish_plugins\n```\n\n```diff\njorgebucaran/fisher\nilancosman/tide@v5\njorgebucaran/nvm.fish\n+ PatrickF1/fzf.fish\n- /home/jb/path/to/plugin\n```\n\n```console\nfisher update\n```\n\nThis will install **PatrickF1**/**fzf.fish**, remove /**home**/**jb**/**path**/**to**/**plugin**, and update everything else.\n\n## Creating a plugin\n\nPlugins can include any number of files in `functions`, `conf.d`, and `completions` directories. Most plugins are just a single function or a [configuration snippet](https://fishshell.com/docs/current/index.html#configuration). Behold the anatomy of a typical plugin:\n\n<pre>\n<b>flipper</b>\n├── <b>completions</b>\n│   └── flipper.fish\n├── <b>conf.d</b>\n│   └── flipper.fish\n└── <b>functions</b>\n    └── flipper.fish\n</pre>\n\nNon `.fish` files and directories inside these locations will be copied to `$fisher_path` under `functions`, `conf.d`, or `completions` respectively.\n\n### Event system\n\nFish [events](https://fishshell.com/docs/current/cmds/emit.html) notify plugins when they're being installed, updated, or removed.\n\n> Keep in mind, `--on-event` functions must be loaded when their event is emitted. So, place your event handlers in the `conf.d` directory.\n\n```fish\n# Defined in flipper/conf.d/flipper.fish\n\nfunction _flipper_install --on-event flipper_install\n    # Set universal variables, create bindings, and other initialization logic.\nend\n\nfunction _flipper_update --on-event flipper_update\n    # Migrate resources, print warnings, and other update logic.\nend\n\nfunction _flipper_uninstall --on-event flipper_uninstall\n    # Erase \"private\" functions, variables, bindings, and other uninstall logic.\nend\n```\n\n## Creating a theme\n\nA theme is like any other Fish plugin, but with a `.theme` file in the `themes` directory. Themes were introduced in [Fish `3.4`](https://github.com/fish-shell/fish-shell/releases/tag/3.4.0) and work with the `fish_config` builtin. A theme can also have files in `functions`, `conf.d`, or `completions` if necessary. Check out what a typical theme plugin looks like:\n\n<pre>\n<b>gills</b>\n├── <b>conf.d</b>\n│   └── gills.fish\n└── <b>themes</b>\n    └── gills.theme\n</pre>\n\n### Using `$fisher_path` with themes\n\nIf you customize `$fisher_path` to use a directory other than `$__fish_config_dir`, your themes won't be available via `fish_config`. That's because Fish expects your themes to be in `$__fish_config_dir/themes`, not `$fisher_path/themes`. This isn't configurable in Fish yet, but there's [a request to add that feature](https://github.com/fish-shell/fish-shell/issues/9456).\n\nFear not! You can easily solve this by symlinking Fisher's `themes` directory into your Fish config. First, backup any existing themes directory.\n\n```console\nmv $__fish_config_dir/themes $__fish_config_dir/themes.bak\n```\n\nNext, create a symlink for Fisher's themes directory.\n\n```console\nln -s $fisher_path/themes $__fish_config_dir/themes\n```\n\nWant to use theme plugins and maintain your own local themes? You can do that too ([#708](https://github.com/jorgebucaran/fisher/issues/708)).\n\n## Discoverability\n\nWhile Fisher doesn't rely on a central plugin repository, discovering new plugins doesn't have to feel like navigating uncharted waters. To boost your plugin's visibility and make it easier for users to find, [add relevant topics to your repository](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/classifying-your-repository-with-topics#adding-topics-to-your-repository) using [`fish-plugin`](https://github.com/topics/fish-plugin). By doing so, you're not only contributing to the Fisher community but also enabling users to explore new plugins and enhance their Fish shell experience. Don't let plugin discovery be a fishy business, tag your plugins today!\n\n## Acknowledgments\n\nFisher started its journey in 2016 by [@jorgebucaran](https://github.com/jorgebucaran) as a shell configuration manager for Fish. Along the way, many helped shape it into what it is today. [Oh My Fish](https://github.com/oh-my-fish/oh-my-fish) paved the way as the first popular Fish framework. [@jethrokuan](https://github.com/jethrokuan) provided crucial support during the early years. [@PatrickF1](https://github.com/PatrickF1)'s candid feedback proved invaluable time and again. Bootstrapping Fisher was originally [@IlanCosman](https://github.com/IlanCosman)'s brilliant idea. Thank you to all our contributors! <3\n\n## License\n\n[MIT](LICENSE.md)\n"
  },
  {
    "path": "completions/fisher.fish",
    "content": "complete --command fisher --exclusive --long help --description \"Print help\"\ncomplete --command fisher --exclusive --long version --description \"Print version\"\ncomplete --command fisher --exclusive --condition __fish_use_subcommand --arguments install --description \"Install plugins\"\ncomplete --command fisher --exclusive --condition __fish_use_subcommand --arguments update --description \"Update installed plugins\"\ncomplete --command fisher --exclusive --condition __fish_use_subcommand --arguments remove --description \"Remove installed plugins\"\ncomplete --command fisher --exclusive --condition __fish_use_subcommand --arguments list --description \"List installed plugins matching regex\"\ncomplete --command fisher --exclusive --condition \"__fish_seen_subcommand_from update remove\" --arguments \"(fisher list)\"\n"
  },
  {
    "path": "functions/fisher.fish",
    "content": "function fisher --argument-names cmd --description \"A plugin manager for Fish\"\n    set --query fisher_path || set --local fisher_path $__fish_config_dir\n    set --local fisher_version 4.4.8\n    set --local fish_plugins $__fish_config_dir/fish_plugins\n\n    switch \"$cmd\"\n        case -v --version\n            echo \"fisher, version $fisher_version\"\n        case \"\" -h --help\n            echo \"Usage: fisher install   <plugins...>  Install plugins\"\n            echo \"       fisher remove    <plugins...>  Remove installed plugins\" \n            echo \"       fisher uninstall <plugins...>  Remove installed plugins (alias)\" \n            echo \"       fisher update    <plugins...>  Update installed plugins\"\n            echo \"       fisher update                  Update all installed plugins\"\n            echo \"       fisher list    [<regex>]       List installed plugins matching regex\"\n            echo \"Options:\"\n            echo \"       -v, --version  Print version\"\n            echo \"       -h, --help     Print this help message\"\n            echo \"Variables:\"\n            echo \"       \\$fisher_path  Plugin installation path. Default: $__fish_config_dir\" | string replace --regex -- $HOME \\~\n        case ls list\n            string match --entire --regex -- \"$argv[2]\" $_fisher_plugins\n        case install update remove uninstall\n            isatty || read --local --null --array stdin && set --append argv $stdin\n\n            test \"$cmd\" = uninstall && set cmd remove\n\n            set --local install_plugins\n            set --local update_plugins\n            set --local remove_plugins\n            set --local arg_plugins $argv[2..-1]\n            set --local old_plugins $_fisher_plugins\n            set --local new_plugins\n\n            test -e $fish_plugins && set --local file_plugins (string match --regex -- '^[^\\s]+$' <$fish_plugins | string replace -- \\~ ~)\n\n            if ! set --query argv[2]\n                if test \"$cmd\" != update\n                    echo \"fisher: Not enough arguments for command: \\\"$cmd\\\"\" >&2 && return 1\n                else if ! set --query file_plugins\n                    echo \"fisher: \\\"$fish_plugins\\\" file not found: \\\"$cmd\\\"\" >&2 && return 1\n                end\n                set arg_plugins $file_plugins\n            else if test \"$cmd\" = install && ! set --query old_plugins[1] \n                set --append arg_plugins $file_plugins\n            end\n\n            for plugin in $arg_plugins\n                set plugin (test -e \"$plugin\" && realpath $plugin || string lower -- $plugin)\n                contains -- \"$plugin\" $new_plugins || set --append new_plugins $plugin\n            end\n\n            if set --query argv[2]\n                for plugin in $new_plugins\n                    if contains -- \"$plugin\" $old_plugins\n                        test \"$cmd\" = remove &&\n                            set --append remove_plugins $plugin ||\n                            set --append update_plugins $plugin\n                    else if test \"$cmd\" = install\n                        set --append install_plugins $plugin\n                    else\n                        echo \"fisher: Plugin not installed: \\\"$plugin\\\"\" >&2 && return 1\n                    end\n                end\n            else\n                for plugin in $new_plugins\n                    contains -- \"$plugin\" $old_plugins &&\n                        set --append update_plugins $plugin ||\n                        set --append install_plugins $plugin\n                end\n\n                for plugin in $old_plugins\n                    contains -- \"$plugin\" $new_plugins || set --append remove_plugins $plugin\n                end\n            end\n\n            set --local pid_list\n            set --local source_plugins\n            set --local fetch_plugins $update_plugins $install_plugins\n            set --local fish_path (status fish-path)\n\n            echo (set_color --bold)fisher $cmd version $fisher_version(set_color normal)\n\n            for plugin in $fetch_plugins\n                set --local source (command mktemp -d)\n                set --append source_plugins $source\n\n                command mkdir -p $source/{completions,conf.d,themes,functions}\n\n                $fish_path --command \"\n                    if test -e $plugin\n                        command cp -Rf $plugin/* $source\n                    else\n                        set resp (command mktemp)\n                        set temp (command mktemp -d)\n                        set repo (string split -- \\@ $plugin) || set repo[2] HEAD\n\n                        if set path (string replace --regex -- '^(https://)?gitlab.com/' '' \\$repo[1])\n                            set name (string split -- / \\$path)[-1]\n                            set url https://gitlab.com/\\$path/-/archive/\\$repo[2]/\\$name-\\$repo[2].tar.gz\n                        else\n                            set url https://api.github.com/repos/\\$repo[1]/tarball/\\$repo[2]\n                        end\n\n                        echo Fetching (set_color --underline)\\$url(set_color normal)\n\n                        set http (command curl -q --silent -L -o \\$resp -w %{http_code} \\$url)\n\n                        if test \\\"\\$http\\\" = 200 && command tar -xzC \\$temp -f \\$resp 2>/dev/null\n                            command cp -Rf \\$temp/*/* $source\n                        else if test \\\"\\$http\\\" = 403\n                            echo fisher: GitHub API rate limit exceeded \\(HTTP 403\\) >&2\n                            command rm -rf $source\n                        else\n                            echo fisher: Invalid plugin name or host unavailable: \\\\\\\"$plugin\\\\\\\" >&2\n                            command rm -rf $source\n                        end\n\n                        command rm -rf \\$temp\n                    end\n\n                    set files $source/* && string match --quiet --regex -- .+\\.fish\\\\\\$ \\$files\n                \" &\n\n                set --append pid_list (jobs --last --pid)\n            end\n\n            wait $pid_list 2>/dev/null\n\n            for plugin in $fetch_plugins\n                if set --local source $source_plugins[(contains --index -- \"$plugin\" $fetch_plugins)] && test ! -e $source\n                    if set --local index (contains --index -- \"$plugin\" $install_plugins)\n                        set --erase install_plugins[$index]\n                    else\n                        set --erase update_plugins[(contains --index -- \"$plugin\" $update_plugins)]\n                    end\n                end\n            end\n\n            for plugin in $update_plugins $remove_plugins\n                if set --local index (contains --index -- \"$plugin\" $_fisher_plugins)\n                    set --local plugin_files_var _fisher_(string escape --style=var -- $plugin)_files\n\n                    if contains -- \"$plugin\" $remove_plugins\n                        for name in (string replace --filter --regex -- '.+/conf\\.d/([^/]+)\\.fish$' '$1' $$plugin_files_var)\n                            emit {$name}_uninstall\n                        end\n                        printf \"%s\\n\" Removing\\ (set_color red --bold)$plugin(set_color normal) \"         \"$$plugin_files_var | string replace -- \\~ ~\n                        set --erase _fisher_plugins[$index]\n                    end\n\n                    command rm -rf (string replace -- \\~ ~ $$plugin_files_var)\n\n                    functions --erase (string replace --filter --regex -- '.+/functions/([^/]+)\\.fish$' '$1' $$plugin_files_var)\n\n                    for name in (string replace --filter --regex -- '.+/completions/([^/]+)\\.fish$' '$1' $$plugin_files_var)\n                        complete --erase --command $name\n                    end\n\n                    set --erase $plugin_files_var\n                end\n            end\n\n            if set --query update_plugins[1] || set --query install_plugins[1]\n                command mkdir -p $fisher_path/{functions,themes,conf.d,completions}\n            end\n\n            for plugin in $update_plugins $install_plugins\n                set --local source $source_plugins[(contains --index -- \"$plugin\" $fetch_plugins)]\n                set --local files $source/{functions,themes,conf.d,completions}/*\n\n                if set --local index (contains --index -- $plugin $install_plugins)\n                    set --local user_files $fisher_path/{functions,themes,conf.d,completions}/*\n                    set --local conflict_files\n\n                    for file in (string replace -- $source/ $fisher_path/ $files)\n                        contains -- $file $user_files && set --append conflict_files $file\n                    end\n\n                    if set --query conflict_files[1] && set --erase install_plugins[$index]\n                        echo -s \"fisher: Cannot install \\\"$plugin\\\": please remove or move conflicting files first:\" \\n\"        \"$conflict_files >&2\n                        continue\n                    end\n                end\n\n                for file in (string replace -- $source/ \"\" $files)\n                    command cp -RLf $source/$file $fisher_path/$file\n                end\n\n                set --local plugin_files_var _fisher_(string escape --style=var -- $plugin)_files\n\n                set --query files[1] && set --universal $plugin_files_var (string replace -- $source $fisher_path $files | string replace -- ~ \\~)\n\n                contains -- $plugin $_fisher_plugins || set --universal --append _fisher_plugins $plugin\n                contains -- $plugin $install_plugins && set --local event install || set --local event update\n\n                printf \"%s\\n\" Installing\\ (set_color --bold)$plugin(set_color normal) \"           \"$$plugin_files_var | string replace -- \\~ ~\n\n                for file in (string match --regex -- '.+/[^/]+\\.fish$' $$plugin_files_var | string replace -- \\~ ~)\n                    source $file\n                    if set --local name (string replace --regex -- '.+conf\\.d/([^/]+)\\.fish$' '$1' $file)\n                        emit {$name}_$event\n                    end\n                end\n            end\n\n            command rm -rf $source_plugins\n\n            if set --query _fisher_plugins[1]\n                set --local commit_plugins\n\n                for plugin in $file_plugins\n                    contains -- (string lower -- $plugin) (string lower -- $_fisher_plugins) && set --append commit_plugins $plugin\n                end\n\n                for plugin in $_fisher_plugins\n                    contains -- (string lower -- $plugin) (string lower -- $commit_plugins) || set --append commit_plugins $plugin\n                end\n\n                string replace --regex -- $HOME \\~ $commit_plugins >$fish_plugins\n            else\n                set --erase _fisher_plugins\n                command rm -f $fish_plugins\n            end\n\n            set --local total (count $install_plugins) (count $update_plugins) (count $remove_plugins)\n\n            test \"$total\" != \"0 0 0\" && echo (string join \", \" (\n                test $total[1] = 0 || echo \"Installed $total[1]\") (\n                test $total[2] = 0 || echo \"Updated $total[2]\") (\n                test $total[3] = 0 || echo \"Removed $total[3]\")\n            ) plugin/s\n        case \\*\n            echo \"fisher: Unknown command: \\\"$cmd\\\"\" >&2 && return 1\n    end\nend\n\nif ! set --query _fisher_upgraded_to_4_4\n    set --universal _fisher_upgraded_to_4_4\n    if functions --query _fisher_list\n        set --query XDG_DATA_HOME[1] || set --local XDG_DATA_HOME ~/.local/share\n        command rm -rf $XDG_DATA_HOME/fisher\n        functions --erase _fisher_{list,plugin_parse}\n        fisher update >/dev/null 2>/dev/null\n    else\n        for var in (set --names | string match --entire --regex '^_fisher_.+_files$')\n            set $var (string replace -- ~ \\~ $$var)\n        end\n        functions --erase _fisher_fish_postexec\n    end\nend\n"
  },
  {
    "path": "tests/fisher.fish",
    "content": "set --local BASENAME --regex -- '[^/]+$'\n\n@echo (fisher --version)\n\n@test \"fisher install\" (\n    fisher install tests/ponyo >/dev/null \n) \"$ponyo\" = \"pyon pyon\"\n\n@test \"fisher list\" (\n    fisher list | string match $BASENAME | string join \" \"\n) = \"fisher fishtape ponyo\"\n\n@test \"fisher list regex\" (\n    fisher list ponyo | string match $BASENAME\n) = ponyo\n\n@test \"pyon pyon\" (fish --command ponyo | string join \" \") = \"pyon pyon ponyo\"\n\n@test \"fisher update\" (\n    fisher update tests/ponyo >/dev/null\n) \"$ponyo\" = \"pyon pyon pyon\"\n\n@test fish_plugins (\n    string match --regex -- \"[^/]+\\$\" <$__fish_config_dir/fish_plugins | string join \" \"\n) = \"fisher fishtape ponyo\"\n\n@test \"fisher remove\" (\n    fisher remove tests/ponyo >/dev/null\n) \"$ponyo\" = \"\"\n\n@test \"has state\" -n (\n    set --names | string match \\*fisher\\* | string collect\n)\n"
  },
  {
    "path": "tests/ponyo/conf.d/ponyo.fish",
    "content": "echo pyon pyon\n\nfunction ponyo_install --on-event ponyo_install\n    set --global ponyo pyon pyon\nend\n\nfunction ponyo_update --on-event ponyo_update\n    set --global --append ponyo pyon\nend\n\nfunction ponyo_uninstall --on-event ponyo_uninstall\n    set --erase ponyo\nend"
  },
  {
    "path": "tests/ponyo/functions/ponyo.fish",
    "content": "function ponyo\n    echo ponyo\nend\n"
  }
]