[
  {
    "path": ".cbfmt.toml",
    "content": "[languages]\nlua = [\"stylua -s -\"]\nsh = [\"shellharden --transform \"]\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace=true\nmax_line_length = 120\ncharset = utf-8\n\n[*.md]\ntrim_trailing_whitespace=false\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [williamboman] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "---\n\ncontact_links:\n  - name: Ask a question about mason.nvim or get support\n    url: https://github.com/mason-org/mason.nvim/discussions/new?category=q-a\n    about: Ask a question or request support for using mason.nvim\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yaml",
    "content": "---\n\nname: Feature request\ndescription: Suggest an idea for this project\nlabels:\n    - enhancement\n\nbody:\n    - type: checkboxes\n      attributes:\n          label: I've searched open issues for similar requests\n          description: If possible, please contribute to any [open issues](https://github.com/mason-org/mason.nvim/issues) instead of opening a new one.\n          options:\n              - label: \"Yes\"\n\n    - type: textarea\n      attributes:\n          label: Is your feature request related to a problem? Please describe.\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Describe the solution you'd like\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Describe potential alternatives you've considered\n\n    - type: textarea\n      attributes:\n          label: Additional context\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/general_issue.yaml",
    "content": "---\nname: Non-package-related issue\ndescription: Report an issue not related to installation or usage of packages\n\nbody:\n    - type: markdown\n      attributes:\n          value: |\n              # Issue reporting guidelines\n\n              1. This is not a general support board for package usage questions (e.g. \"How do I do X?\"). For questions, please refer to [the discussion board](https://github.com/mason-org/mason.nvim/discussions/categories/q-a) first! :)\n              1. Before reporting an issue, make sure that you meet the minimum requirements mentioned in the README. Also review `:checkhealth mason` for potential problems.\n\n              ---\n\n    - type: checkboxes\n      attributes:\n          label: I've searched open issues for similar requests\n          description: If possible, please contribute to any [open issues](https://github.com/mason-org/mason.nvim/issues?q=is%3Aissue) instead of opening a new one.\n          options:\n              - label: \"Yes\"\n\n    - type: checkboxes\n      attributes:\n          label: I've recently downloaded the latest plugin version of mason.nvim\n          options:\n              - label: \"Yes\"\n\n    - type: textarea\n      attributes:\n          label: Problem description\n          description: A clear and short description of 1) what the issue is, and 2) why you think it's an issue with mason.nvim.\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Expected behavior\n          description: A short description of the behavior you expected.\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Steps to reproduce\n          placeholder: |\n              1. ...\n              2. ...\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: \"Neovim version (>= 0.10.0)\"\n          description: \"Output of `nvim --version`\"\n          placeholder: |\n              NVIM v0.10.0-dev\n              Build type: Release\n              LuaJIT 2.1.0-beta3\n      validations:\n          required: true\n\n    - type: input\n      attributes:\n          label: \"Operating system/version\"\n          description: \"On Linux and Mac systems: `$ uname -a`\"\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Healthcheck output\n          placeholder: \":checkhealth mason\"\n          render: shell\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Screenshots\n          description: If applicable, add screenshots to help explain your problem\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/new_package_request.yaml",
    "content": "---\n\nname: New package request\ndescription: Request a new package not currently available\ntitle: \"[New package]: \"\nlabels:\n    - new-package-request\n\nbody:\n    - type: checkboxes\n      attributes:\n          label: I've searched open & closed issues for similar requests\n          description: If possible, please contribute to [existing issues](https://github.com/mason-org/mason.nvim/issues?q=is%3Aissue+label%3Anew-package-request) instead of opening a new one.\n          options:\n              - label: \"Yes\"\n\n    - type: input\n      attributes:\n          label: Package name\n          description: Which package would you like to request to be added?\n      validations:\n          required: true\n\n    - type: input\n      attributes:\n          label: Package homepage\n          description: e.g., a GitHub page\n      validations:\n          required: true\n\n    - type: input\n      attributes:\n          label: Languages\n          description: Which languages does this package target?\n          placeholder: typescript, javascript\n\n    - type: textarea\n      attributes:\n          label: How is this package distributed?\n          description: Is the package distributed through a standardized channel (such as GitHub release files, npm, pip, etc.)? Leave empty if you don't know.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/package_installation_form.yaml",
    "content": "---\n\nname: Package installation issue\ndescription: Report an issue that occurs during the installation of a package\nlabels:\n    - installation-issue\n\nbody:\n    - type: markdown\n      attributes:\n          value: |\n              # Issue reporting guidelines\n\n              1. Before reporting an issue, make sure that you meet the minimum requirements mentioned in the README. Also review `:checkhealth mason` for potential problems.\n              1. Please try to review the error yourself first and ensure it's not a problem that is local to your system only.\n\n              ---\n\n    - type: checkboxes\n      attributes:\n          label: I've searched open issues for similar requests\n          description: If possible, please contribute to any [open issues](https://github.com/mason-org/mason.nvim/issues?q=is%3Aissue+label%3Ainstallation-issue) instead of opening a new one.\n          options:\n              - label: \"Yes\"\n\n    - type: checkboxes\n      attributes:\n          label: I've recently downloaded the latest plugin version of mason.nvim\n          options:\n              - label: \"Yes\"\n\n    - type: textarea\n      attributes:\n          label: Problem description\n          description: A clear and short description of 1) what the issue is, and 2) why you think it's an issue with mason.nvim.\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Expected behavior\n          description: A short description of the behavior you expected.\n      validations:\n          required: true\n\n    - type: input\n      attributes:\n          label: Affected packages\n          description: If this issue is specific to one or more packages, list them here. If not, write 'All'.\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Mason output\n          description: Please provide the **installation output** available in the `:Mason` window, if possible.\n          placeholder: \"Please only provide the output of the package installation.\"\n          render: Text\n\n    - type: textarea\n      attributes:\n          label: Installation log\n          description: \"`:MasonLog`. Refer to `:help mason-debugging`\"\n          placeholder: \"The default log level is not helpful for debugging purposes! Make sure you set the log level to DEBUG before installing the package (:h mason-debugging).\"\n          render: Text\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: \"Neovim version (>= 0.10.0)\"\n          description: \"Output of `nvim --version`\"\n          placeholder: |\n              NVIM v0.7.0-dev\n              Build type: Release\n              LuaJIT 2.1.0-beta3\n      validations:\n          required: true\n\n    - type: input\n      attributes:\n          label: \"Operating system/version\"\n          description: \"On Linux and Mac systems: `$ uname -a`\"\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Healthcheck\n          placeholder: \":checkhealth mason\"\n          render: Text\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Screenshots\n          description: If applicable, add screenshots to help explain your problem\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/package_issue.yaml",
    "content": "---\n\nname: Package issue\ndescription: Report an issue with using a package installed via mason.nvim\nlabels:\n    - package-issue\n\nbody:\n    - type: markdown\n      attributes:\n          value: |\n              # Issue reporting guidelines\n\n              1. This is not a general support board for package usage questions (e.g. \"How do I do X?\"). For questions, please refer to [the discussion board](https://github.com/mason-org/mason.nvim/discussions/categories/q-a) first! :)\n              1. Before reporting an issue, make sure that you meet the minimum requirements mentioned in the README. Also review `:checkhealth mason` for potential problems.\n              1. General usage issues with packages should not be reported here. Please only report issues that you believe are a result of something that mason.nvim does.\n              1. Please try to review errors yourself first and ensure it's not a problem that is local to your system only.\n\n              ---\n\n    - type: checkboxes\n      attributes:\n          label: I've searched open issues for similar requests\n          description: If possible, please contribute to any open issues instead of opening a new one.\n          options:\n              - label: \"Yes\"\n\n    - type: checkboxes\n      attributes:\n          label: I've manually reviewed logs to find potential errors\n          description: Logs such as `:MasonLog`, `:LspLog`, etc. (don't paste logs without reviewing them yourself first)\n          options:\n              - label: \"Yes\"\n\n    - type: checkboxes\n      attributes:\n          label: I've recently downloaded the latest plugin version of mason.nvim\n          options:\n              - label: \"Yes\"\n\n    - type: textarea\n      attributes:\n          label: Problem description\n          description: A clear and short description of 1) what the issue is, and 2) why you think it's an issue with mason.nvim.\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Expected behavior\n          description: A description of the behavior you expected.\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Steps to reproduce\n          placeholder: |\n              1. ...\n              2. ...\n      validations:\n          required: true\n\n    - type: input\n      attributes:\n          label: Affected packages\n          description: If this issue is specific to one or more packages, list them here. If not, write 'All'.\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: \"Neovim version (>= 0.10.0)\"\n          description: \"Output of `nvim --version`\"\n          placeholder: |\n              NVIM v0.10.0-dev\n              Build type: Release\n              LuaJIT 2.1.0-beta3\n      validations:\n          required: true\n\n    - type: input\n      attributes:\n          label: \"Operating system/version\"\n          description: \"On Linux and Mac systems: `$ uname -a`\"\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: Healthcheck\n          placeholder: \":checkhealth mason\"\n          render: Text\n      validations:\n          required: true\n\n    - type: textarea\n      id: screenshots\n      attributes:\n          label: Screenshots or recordings\n          description: If applicable, add screenshots or recordings to help explain your problem.\n"
  },
  {
    "path": ".github/workflows/cbfmt.yml",
    "content": "name: cbfmt check\n\non:\n    push:\n        branches:\n            - \"main\"\n    pull_request:\n\njobs:\n    cbfmt:\n        runs-on: ubuntu-latest\n        steps:\n            - uses: actions/checkout@v4\n            - name: Download cbfmt\n              run: |\n                  mkdir /tmp/cbfmt && cd $_\n                  curl -fsSL -o cbfmt.tar.gz \"https://github.com/lukas-reineke/cbfmt/releases/download/v0.2.0/cbfmt_linux-x86_64_v0.2.0.tar.gz\"\n                  tar --strip-components 1 -xvf cbfmt.tar.gz\n                  mv cbfmt /usr/local/bin/\n            - name: Download Stylua\n              run: |\n                  mkdir /tmp/stylua && cd $_\n                  curl -fsSL -o stylua.zip \"https://github.com/JohnnyMorganz/StyLua/releases/download/v0.20.0/stylua-linux.zip\"\n                  unzip -d /usr/local/bin stylua.zip\n            - name: Download Shellharden\n              run: |\n                  mkdir /tmp/shellharden && cd $_\n                  curl -fsSL -o shellharden.tar.gz https://github.com/anordal/shellharden/releases/download/v4.3.1/shellharden-x86_64-unknown-linux-gnu.tar.gz\n                  tar -xvf shellharden.tar.gz\n                  mv shellharden /usr/local/bin/\n            - name: Run cbfmt check\n              # Lua examples in README.md doesn't conform to Stylua rules, on purpose.\n              run: find . -name '*.md' -not -path './dependencies/*' -not -path './README.md' -not -path './CHANGELOG.md' | xargs cbfmt --check\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    branches:\n      - \"main\"\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  release:\n    name: release\n    runs-on: ubuntu-latest\n    steps:\n      - uses: google-github-actions/release-please-action@v3\n        id: release\n        with:\n          token: ${{ secrets.PAT }}\n          release-type: simple\n          package-name: mason.nvim\n          extra-files: |\n            README.md\n            lua/mason/version.lua\n\n      - uses: actions/checkout@v4\n\n      - uses: rickstaa/action-create-tag@v1\n        if: ${{ steps.release.outputs.release_created }}\n        with:\n          tag: stable\n          message: \"Current stable release: ${{ steps.release.outputs.tag_name }}\"\n          force_push_tag: true\n"
  },
  {
    "path": ".github/workflows/selene.yml",
    "content": "name: Selene check\n\non:\n    push:\n        branches:\n            - \"main\"\n    pull_request:\n\njobs:\n    selene:\n        runs-on: ubuntu-latest\n        steps:\n            - uses: actions/checkout@v4\n            - name: Run Selene check\n              uses: NTBBloodbath/selene-action@v1.0.0\n              with:\n                  # token is needed because the action allegedly downloads binary from github releases\n                  token: ${{ secrets.GITHUB_TOKEN }}\n                  args: lua/ tests/\n                  version: 0.27.1\n"
  },
  {
    "path": ".github/workflows/stylua.yml",
    "content": "name: Stylua check\n\non:\n    push:\n        branches:\n            - \"main\"\n    pull_request:\n\njobs:\n    stylua:\n        runs-on: ubuntu-latest\n        steps:\n            - uses: actions/checkout@v4\n            - name: Run Stylua check\n              uses: JohnnyMorganz/stylua-action@v1.1.1\n              with:\n                  # token is needed because the action allegedly downloads binary from github releases\n                  token: ${{ secrets.GITHUB_TOKEN }}\n                  # CLI arguments\n                  args: --check .\n                  version: 0.20.0\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\n\non:\n    push:\n        branches:\n            - \"main\"\n    pull_request:\n\njobs:\n    tests:\n        strategy:\n            fail-fast: false\n            matrix:\n                nvim_version:\n                    - v0.10.0\n                    - v0.10.1\n                    - v0.10.2\n                    - v0.10.3\n                    - v0.10.4\n                    - v0.11.0\n                    - v0.11.1\n        runs-on: ubuntu-latest\n        steps:\n            - uses: actions/checkout@v4\n            - uses: rhysd/action-setup-vim@v1\n              with:\n                  neovim: true\n                  version: ${{ matrix.nvim_version }}\n            - name: Run tests\n              run: |\n                set -e\n                make test\n                nvim -u NONE -E -R --headless +'helptags doc' +q\n"
  },
  {
    "path": ".gitignore",
    "content": ".luarc.json\n/dependencies\n/tests/fixtures/mason\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [2.2.1](https://github.com/mason-org/mason.nvim/compare/v2.2.0...v2.2.1) (2026-01-07)\n\n\n### Bug Fixes\n\n* **registry:** exclude synthesized registry when updating/installing registries ([#2054](https://github.com/mason-org/mason.nvim/issues/2054)) ([3fce8bd](https://github.com/mason-org/mason.nvim/commit/3fce8bd25e773bae4267c9e8f2cfbfda22aeb017))\n\n## [2.2.0](https://github.com/mason-org/mason.nvim/compare/v2.1.0...v2.2.0) (2026-01-07)\n\n\n### Features\n\n* add support for removal of packages from a registry ([#2052](https://github.com/mason-org/mason.nvim/issues/2052)) ([69862d6](https://github.com/mason-org/mason.nvim/commit/69862d6c8dbe215489c3e48e624ff25f44437e55))\n\n\n### Bug Fixes\n\n* **installer:** attempt to recover from known fs error while finalizing installation on some file systems ([#1933](https://github.com/mason-org/mason.nvim/issues/1933)) ([198f075](https://github.com/mason-org/mason.nvim/commit/198f07572c0014774fb87371946e0f03b4908bce))\n* **installer:** update cwd after uv_fs_rename() was successful ([#2033](https://github.com/mason-org/mason.nvim/issues/2033)) ([57e5a8a](https://github.com/mason-org/mason.nvim/commit/57e5a8addb8c71fb063ee4acda466c7cf6ad2800))\n\n## [2.1.0](https://github.com/mason-org/mason.nvim/compare/v2.0.1...v2.1.0) (2025-09-30)\n\n\n### Features\n\n* **compiler:** make `supported_platforms` a universal source field ([#2002](https://github.com/mason-org/mason.nvim/issues/2002)) ([7dc4fac](https://github.com/mason-org/mason.nvim/commit/7dc4facca9702f95353d5a1f87daf23d78e31c2a))\n\n\n### Bug Fixes\n\n* **process:** close check handles ([#1995](https://github.com/mason-org/mason.nvim/issues/1995)) ([a1fbecc](https://github.com/mason-org/mason.nvim/commit/a1fbecc0fd76300e8fe84879fb1531f35cf7b018))\n* **pypi:** add support for \"compatible release\" (~=) PEP440 expressions ([#2000](https://github.com/mason-org/mason.nvim/issues/2000)) ([9e25c98](https://github.com/mason-org/mason.nvim/commit/9e25c98d4826998460926f8c5c2284848d80ae89))\n* **spawn:** always expand executable path on Windows ([#2021](https://github.com/mason-org/mason.nvim/issues/2021)) ([a83eabd](https://github.com/mason-org/mason.nvim/commit/a83eabdc8c49c0c93bf5bb162fa3b57404a9d095))\n* **ui:** only set border to none if `'winborder'` doesn't exist ([#1984](https://github.com/mason-org/mason.nvim/issues/1984)) ([3671ab0](https://github.com/mason-org/mason.nvim/commit/3671ab0d40aa5bd24b1686562bd0a23391ecf76a))\n\n## [2.0.1](https://github.com/mason-org/mason.nvim/compare/v2.0.0...v2.0.1) (2025-07-25)\n\n\n### Bug Fixes\n\n* **fetch:** add busybox wget support ([#1829](https://github.com/mason-org/mason.nvim/issues/1829)) ([8024d64](https://github.com/mason-org/mason.nvim/commit/8024d64e1330b86044fed4c8494ef3dcd483a67c))\n* **pypi:** pass --no-user flag ([#1958](https://github.com/mason-org/mason.nvim/issues/1958)) ([1aceba8](https://github.com/mason-org/mason.nvim/commit/1aceba8bc158b5aaf90649077cad06744bc23ac4))\n* **registry:** ensure there's no duplicate registry entries ([#1957](https://github.com/mason-org/mason.nvim/issues/1957)) ([3501b0f](https://github.com/mason-org/mason.nvim/commit/3501b0f96d9f2f878b1947cf3614bc02d053a0c0))\n* **spawn:** fix calling vim.fn when inside fast event loop on Windows ([#1950](https://github.com/mason-org/mason.nvim/issues/1950)) ([888d6ee](https://github.com/mason-org/mason.nvim/commit/888d6ee499d8089a3a4be4309d239d6be1c1e6c0))\n* **spawn:** fix locating exepath on Windows systems using a Unix `'shell'` ([#1991](https://github.com/mason-org/mason.nvim/issues/1991)) ([edd8f7b](https://github.com/mason-org/mason.nvim/commit/edd8f7bce8f86465349b24e235718eb3ea52878d))\n\n## [2.0.0](https://github.com/mason-org/mason.nvim/compare/v1.11.0...v2.0.0) (2025-05-06)\n\nThis release has been an ongoing effort for quite some time now and is now ready for release. Most users should not\nexperience any breaking changes. If you use any of the Lua APIs that Mason provides you'll find an outline of the\nchanges below, breaking changes are marked with `Breaking Change`.\n\n### Repository has been moved\n\nThe repository has been transferred to the [`mason-org`](https://github.com/mason-org) organization. The new URL is\nhttps://github.com/mason-org/mason.nvim. The previous URL will continue to function as a redirect to the new URL but\nusers are recommended to update to the new location. \n\n### Addition of new maintainers ❤️\n\n- [@mehalter](https://github.com/mehalter)\n- [@Conarius](https://github.com/Conarius)\n- [@chrisgrieser](https://github.com/chrisgrieser)\n\n### Features\n- Symlinks now uses relative paths instead of absolute paths.\n- Uninstalled packages now display their available version in the `:Mason` UI.\n- Packages in the `:Mason` UI now display the source [`purl`](https://github.com/package-url/purl-spec).\n- Official support for [custom registries](https://github.com/mason-org/registry-examples).\n- Make registry installations run concurrently.\n- Add support for `'winborder'`.\n- Display current `mason.nvim` version in the `:Mason` UI header.\n\n### Bug Fixes\n- Only attempt unlinking package if the receipt is found.\n- Expand executable paths on Windows before passing to uv_spawn.\n- Fix initializing UI state when using multiple registries.\n- Fix the display of outdated packages in the Mason UI under certain conditions.\n\n### Misc\n- `Breaking Change` Minimum Neovim requirement changed from 0.7.0 to 0.10.0. \n- `Breaking Change` APIs related to custom packages written in Lua has been removed.\n    - All `require(\"mason-core.installer.managers\")` modules have been removed.\n    - The package structure of Lua packages has changed, refer to [custom\n      registries](https://github.com/mason-org/registry-examples) for information on how to continue using custom\n      packages in Lua.\n\n### Event changes\n\n#### Package\n- `Breaking Change` `install:success` now provides the receipt as payload argument.\n- `Breaking Change` `install:failed` now provides the error as payload argument.\n- `Breaking Change` `uninstall:success` now provides the receipt of the uninstalled package as payload argument.\n- `uninstall:failed` is now emitted when package uninstallation fails.\n\n#### Registry\n- `Breaking Change` `package:install:success` now provides the receipt as payload argument.\n- `Breaking Change` `package:install:failed` now provides the error as payload argument.\n- `Breaking Change` `package:uninstall:success` now provides the receipt of the uninstalled package as payload argument.\n- `package:uninstall:failed` is now emitted when package uninstallation fails.\n- `Breaking Change` `update` is no longer emitted when registry is updated. It's replaced by the following events:\n  - `update:start` when the registry starts updating\n  - `update:success` when the registry is successfully updated\n  - `update:failed` when the registry failed to update\n  - `update:progress` is emitted when the registry update process makes progress when multiple registries are used\n\n### Package API changes\n\n#### `Package:get_install_path()` has been removed.\n`Breaking Change`\n\nThis method has been removed to prepare for future changes.\n\nIf you're using this method to access an executable, please consider simply using the canonical name of the executable\nas Mason adds these to your `PATH` by default. If you're using the method to access other files inside the package,\nplease consider accessing the `$MASON/share` directory instead.\n\nExample:\n\n_Clarification: The `$MASON` environment variable has been available since v1.0.0._\n\n```lua\n-- 1a. There's no need to reach into the package directory via Package:get_install_path() to access the executable\nprint(vim.fn.exepath(\"kotlin-debug-adapter\"))\n-- /Users/william/.local/share/nvim/mason/bin/kotlin-debug-adapter\n\n-- 1b. Alternatively if you've configured Mason to not modify PATH\nprint(vim.fn.expand(\"$MASON/bin/kotlin-debug-adapter\"))\n-- /Users/william/.local/share/nvim/mason/bin/kotlin-debug-adapter\n\n-- 2. To access other files inside the package directory, consider accessing them via the share/ directory\nvim.print(vim.fn.globpath(\"$MASON/share/java-debug-adapter\", \"*.jar\", true, true))\n-- { \"/Users/william/.local/share/nvim/mason/share/java-debug-adapter/com.microsoft.java.debug.plugin-0.53.1.jar\", \"/Users/william/.local/share/nvim/mason/share/java-debug-adapter/com.microsoft.java.debug.plugin.jar\" }\n\n-- 3. If you absolutely need to access the package directory (please consider raising an issue/PR in the registry if possible)\nprint(vim.fn.expand(\"$MASON/packages/kotlin-debug-adapter/adapter/bin/kotlin-debug-adapter\"))\n-- /Users/william/.local/share/nvim/mason/packages/kotlin-debug-adapter/adapter/bin/kotlin-debug-adapter\n```\n\n> [!NOTE]\n> Why was this method removed? The contents of the package directory is not a stable interface and its structure may\n> change without prior notice, for example to host multiple versions of a package. The only stable interfaces on the\n> file system are files available in `bin/`, `share/` and `opt/` - these directories are only subject to breaking\n> changes done by the underlying package itself.\n\n---\n\n#### `Package:uninstall(opts, callback)` is now asynchronous.\n`Breaking Change`\n\nThis method now provides an asynchronous interface and accepts two new optional arguments `opts` and `callback`. `opts`\ncurrently doesn't have any valid values other than an empty Lua table `{}`. `callback` is called when the package is\nuninstalled, successfully or not. While the uninstall mechanism under the hood remains synchronous for the time being it\nis not a guarantee going forward and users are recommended to always use the asynchronous version.\n\nExample:\n\n```lua\nlocal registry = require(\"mason-registry\")\nlocal pkg = registry.get_package(\"lua-language-server\")\n\npkg:uninstall({}, function (success, result)\n    if success then\n        -- Do something on success.\n    else\n        -- Do something on error.\n    end\nend)\n```\n\n---\n\n#### `Package:check_new_version()` has been removed.\n`Breaking Change`\n\n`Package:check_new_version()` is replaced by `Package:get_latest_version()`. `Package:get_latest_version()` is a\nsynchronous API.\n\n> [!NOTE]\n> Similarly to before, this function returns the package version provided by the currently installed registry version.\n\nExample:\n```lua\nlocal registry = require(\"mason-registry\")\nlocal pkg = registry.get_package(\"lua-language-server\")\nlocal latest_version = pkg:get_latest_version()\n```\n\n---\n\n#### `Package:get_installed_version()` is now synchronous.\n`Breaking Change`\n\nThis function no longer accepts a callback.\n\nExample:\n```lua\nlocal registry = require(\"mason-registry\")\nlocal pkg = registry.get_package(\"lua-language-server\")\nif pkg:is_installed() then\n    local installed_version = pkg:get_installed_version()\nend\n```\n\n---\n\n#### `Package:install()` will now error if the package is currently being installed.\n`Breaking Change`\n\nUse the new `Package:is_installing()` method to check whether an installation is already running.\n\n---\n\n#### `Package:uninstall()` will now error if the package is not already installed.\n`Breaking Change`\n\nUse the new `Package:is_installed()` method to check whether the package is installed.\n\n---\n\n#### `Package:install(opts, callback)` now accepts a callback.\n\nThis optional callback is called by Mason when package installation finishes, successfully or not.\n\nExample:\n\n```lua\nlocal registry = require(\"mason-registry\")\nlocal pkg = registry.get_package(\"lua-language-server\")\n\npkg:install({}, function (success, result)\n    if success then\n        -- Do something on success.\n    else\n        -- Do something on error.\n    end\nend)\n```\n\n### Custom registries\n\nv2.0.0 introduces official support for custom registries. Currently supported registry protocols are `github:`, `file:`,\nand `lua:`. Lua-based registries have been reworked, please see https://github.com/mason-org/registry-examples for examples. \n\nThanks to all sponsors who continue to help finance monthly costs and all 181 contributors of mason.nvim and 246\ncontributors of the core registry!\n\n## [1.11.0](https://github.com/williamboman/mason.nvim/compare/v1.10.0...v1.11.0) (2025-02-15)\n\n\n### Features\n\n* **pypi:** improve resolving suitable python version ([#1725](https://github.com/williamboman/mason.nvim/issues/1725)) ([0950b15](https://github.com/williamboman/mason.nvim/commit/0950b15060067f752fde13a779a994f59516ce3d))\n* **ui:** add backdrop ([#1759](https://github.com/williamboman/mason.nvim/issues/1759)) ([0a3a85f](https://github.com/williamboman/mason.nvim/commit/0a3a85fa1a59e0bb0811c87556dee51f027b3358))\n\n\n### Bug Fixes\n\n* avoid calling vim.fn in fast event ([#1878](https://github.com/williamboman/mason.nvim/issues/1878)) ([3a444cb](https://github.com/williamboman/mason.nvim/commit/3a444cb7b0cee6b1e2ed31b7e76f37509075dc46))\n* avoid calling vim.fn.has inside fast event ([#1705](https://github.com/williamboman/mason.nvim/issues/1705)) ([1b3d604](https://github.com/williamboman/mason.nvim/commit/1b3d60405d1d720b2c4927f19672e9479703b00f))\n* fix usage of deprecated Neovim APIs ([#1703](https://github.com/williamboman/mason.nvim/issues/1703)) ([0f1cb65](https://github.com/williamboman/mason.nvim/commit/0f1cb65f436b769733d18b41572f617a1fb41f62))\n* **fs:** fall back to `fs_stat` if entry type is not returned by `fs_readdir` ([#1783](https://github.com/williamboman/mason.nvim/issues/1783)) ([1114b23](https://github.com/williamboman/mason.nvim/commit/1114b2336e917d883c30f89cd63ba94050001b2d))\n* **health:** support multidigit luarocks version numbers ([#1648](https://github.com/williamboman/mason.nvim/issues/1648)) ([751b1fc](https://github.com/williamboman/mason.nvim/commit/751b1fcbf3d3b783fcf8d48865264a9bcd8f9b10))\n* **pypi:** allow access to system site packages by default ([#1584](https://github.com/williamboman/mason.nvim/issues/1584)) ([2be2600](https://github.com/williamboman/mason.nvim/commit/2be2600f9b5a61b0c6109a3fb161b3abe75e5195))\n* **pypi:** exclude python3.12 from candidate list ([#1722](https://github.com/williamboman/mason.nvim/issues/1722)) ([f8ce876](https://github.com/williamboman/mason.nvim/commit/f8ce8768f296717c72b3910eee7bd5ac5223cdb9))\n* **pypi:** prefer stock python3 if it satisfies version requirement ([#1736](https://github.com/williamboman/mason.nvim/issues/1736)) ([f96a318](https://github.com/williamboman/mason.nvim/commit/f96a31855fa8aea55599cea412fe611b85a874ed))\n* **registry:** exhaust streaming parser when loading \"file:\" registries ([#1708](https://github.com/williamboman/mason.nvim/issues/1708)) ([49ff59a](https://github.com/williamboman/mason.nvim/commit/49ff59aded1047a773670651cfa40e76e63c6377))\n* replace deprecated calls to vim.validate ([#1876](https://github.com/williamboman/mason.nvim/issues/1876)) ([5664dd5](https://github.com/williamboman/mason.nvim/commit/5664dd5deb3ac9527da90691543eb28df51c1ef8))\n* **ui:** fix rendering JSON schemas ([#1757](https://github.com/williamboman/mason.nvim/issues/1757)) ([e2f7f90](https://github.com/williamboman/mason.nvim/commit/e2f7f9044ec30067bc11800a9e266664b88cda22))\n* **ui:** reposition window if border is different than \"none\" ([#1859](https://github.com/williamboman/mason.nvim/issues/1859)) ([f9f3b46](https://github.com/williamboman/mason.nvim/commit/f9f3b464dda319288b8ce592e53f0d9cf9ca8b4e))\n\n\n### Performance Improvements\n\n* **registry:** significantly improve the \"file:\" protocol performance ([#1702](https://github.com/williamboman/mason.nvim/issues/1702)) ([098a56c](https://github.com/williamboman/mason.nvim/commit/098a56c385ca3a1a0d4682d129203dda35421b8e))\n\n## [1.10.0](https://github.com/williamboman/mason.nvim/compare/v1.9.0...v1.10.0) (2024-01-29)\n\n\n### Features\n\n* don't use vim.g.python3_host_prog as a candidate for python ([#1606](https://github.com/williamboman/mason.nvim/issues/1606)) ([bce96d2](https://github.com/williamboman/mason.nvim/commit/bce96d2fd483e71826728c6f9ac721fc9dd7d2cf))\n* **pypi:** attempt more python3 candidates ([#1608](https://github.com/williamboman/mason.nvim/issues/1608)) ([dcd0ea3](https://github.com/williamboman/mason.nvim/commit/dcd0ea30ccfc7d47e879878d1270d6847a519181))\n\n\n### Bug Fixes\n\n* **golang:** fix fetching package versions for packages containing subpath specifier ([#1607](https://github.com/williamboman/mason.nvim/issues/1607)) ([9c94168](https://github.com/williamboman/mason.nvim/commit/9c9416817c9f4e6f333c749327a1ed5355cfab61))\n* **pypi:** fix variable shadowing ([#1610](https://github.com/williamboman/mason.nvim/issues/1610)) ([aa550fb](https://github.com/williamboman/mason.nvim/commit/aa550fb0649643eee89d5e64c67f81916e88a736))\n* **ui:** don't indent empty lines ([#1597](https://github.com/williamboman/mason.nvim/issues/1597)) ([c7e6705](https://github.com/williamboman/mason.nvim/commit/c7e67059bb8ce7e126263471645c531d961b5e1d))\n\n## [1.9.0](https://github.com/williamboman/mason.nvim/compare/v1.8.3...v1.9.0) (2024-01-06)\n\n\n### Features\n\n* add support for openvsx sources ([#1589](https://github.com/williamboman/mason.nvim/issues/1589)) ([6c68547](https://github.com/williamboman/mason.nvim/commit/6c685476df4f202e371bdd3d726729d6f3f8b9f0))\n\n\n### Bug Fixes\n\n* **cargo:** don't attempt to fetch versions when version targets commit SHA ([#1585](https://github.com/williamboman/mason.nvim/issues/1585)) ([a09da6a](https://github.com/williamboman/mason.nvim/commit/a09da6ac634926a299dd439da08bdb547a8ca011))\n\n## [1.8.3](https://github.com/williamboman/mason.nvim/compare/v1.8.2...v1.8.3) (2023-11-08)\n\n\n### Bug Fixes\n\n* **pypi:** support MSYS2 virtual environments on Windows ([#1547](https://github.com/williamboman/mason.nvim/issues/1547)) ([3e2432a](https://github.com/williamboman/mason.nvim/commit/3e2432ad0bca01fc3356389b341aa3e5e2da9fd8))\n\n## [1.8.2](https://github.com/williamboman/mason.nvim/compare/v1.8.1...v1.8.2) (2023-10-31)\n\n\n### Bug Fixes\n\n* **registry:** fix parsing registry identifiers that contain \":\" ([#1542](https://github.com/williamboman/mason.nvim/issues/1542)) ([87eb3ac](https://github.com/williamboman/mason.nvim/commit/87eb3ac2ab4fcbf5326d8bde6842b073a3be65a7))\n\n## [1.8.1](https://github.com/williamboman/mason.nvim/compare/v1.8.0...v1.8.1) (2023-10-10)\n\n\n### Bug Fixes\n\n* **health:** schedule vim.fn call ([#1514](https://github.com/williamboman/mason.nvim/issues/1514)) ([3ba3b79](https://github.com/williamboman/mason.nvim/commit/3ba3b79f73d5411e72c7df5445150f4e9278d4d7))\n\n## [1.8.0](https://github.com/williamboman/mason.nvim/compare/v1.7.0...v1.8.0) (2023-09-04)\n\n\n### Features\n\n* **ui:** add setting to toggle help view ([#1468](https://github.com/williamboman/mason.nvim/issues/1468)) ([e1602c8](https://github.com/williamboman/mason.nvim/commit/e1602c868f938877057cb6f45e50859cb55cad96))\n\n\n### Bug Fixes\n\n* **registry:** reset registries state when setting registries ([#1474](https://github.com/williamboman/mason.nvim/issues/1474)) ([c811fbf](https://github.com/williamboman/mason.nvim/commit/c811fbf09c7642eebb37d6694f1a016a043f6ed3))\n* **registry:** schedule vim.fn calls in FileRegistrySource ([#1471](https://github.com/williamboman/mason.nvim/issues/1471)) ([1c77412](https://github.com/williamboman/mason.nvim/commit/1c77412d7ff73e453cdc5366c8d7cd98d2242802))\n\n## [1.7.0](https://github.com/williamboman/mason.nvim/compare/v1.6.2...v1.7.0) (2023-08-25)\n\n\n### Features\n\n* **cargo:** support fetching versions for git crates hosted on github ([#1459](https://github.com/williamboman/mason.nvim/issues/1459)) ([e9eb004](https://github.com/williamboman/mason.nvim/commit/e9eb0048cecc577a1eec534485d3e010487b46a7))\n* **registry:** add file: source protocol ([#1457](https://github.com/williamboman/mason.nvim/issues/1457)) ([8544039](https://github.com/williamboman/mason.nvim/commit/85440397264a31208721e4501c93b23a4940b27e))\n\n\n### Bug Fixes\n\n* **std:** use gtar if available ([#1433](https://github.com/williamboman/mason.nvim/issues/1433)) ([a51c2d0](https://github.com/williamboman/mason.nvim/commit/a51c2d063c5377ee9e58c5f9cda7c7436787be72))\n* **ui:** properly reset new package version state ([#1454](https://github.com/williamboman/mason.nvim/issues/1454)) ([68e6a15](https://github.com/williamboman/mason.nvim/commit/68e6a153d7cd1251eb85ebb48d2e351e9ab940b8))\n\n## [1.6.2](https://github.com/williamboman/mason.nvim/compare/v1.6.1...v1.6.2) (2023-08-09)\n\n\n### Bug Fixes\n\n* **ui:** don't disable search mode if empty pattern and last-pattern is set ([#1445](https://github.com/williamboman/mason.nvim/issues/1445)) ([be6f680](https://github.com/williamboman/mason.nvim/commit/be6f680774a75a06ceede3bd7159df2388f49b04))\n\n## [1.6.1](https://github.com/williamboman/mason.nvim/compare/v1.6.0...v1.6.1) (2023-07-21)\n\n\n### Bug Fixes\n\n* **installer:** retain unmapped source fields ([#1399](https://github.com/williamboman/mason.nvim/issues/1399)) ([0579574](https://github.com/williamboman/mason.nvim/commit/05795741895ee16062eabeb0d89bff7cbcd693fa))\n\n## [1.6.0](https://github.com/williamboman/mason.nvim/compare/v1.5.1...v1.6.0) (2023-07-04)\n\n\n### Features\n\n* **ui:** display package deprecation message ([#1391](https://github.com/williamboman/mason.nvim/issues/1391)) ([b728115](https://github.com/williamboman/mason.nvim/commit/b7281153cd9167d2b1a5d8cbda1ba8d4ad9fa8c2))\n* **ui:** don't use diagnostic messages for displaying deprecated, uninstalled, packages ([#1393](https://github.com/williamboman/mason.nvim/issues/1393)) ([c290d0e](https://github.com/williamboman/mason.nvim/commit/c290d0e4ab6da9cac1e26684e53fba0b615862ed))\n\n## [1.5.1](https://github.com/williamboman/mason.nvim/compare/v1.5.0...v1.5.1) (2023-06-28)\n\n\n### Bug Fixes\n\n* **linker:** ensure exec wrapper target is executable ([#1380](https://github.com/williamboman/mason.nvim/issues/1380)) ([10da1a3](https://github.com/williamboman/mason.nvim/commit/10da1a33b4ac24ad4d76a9af91871720ac6b65e4))\n* **purl:** percent-encoding is case insensitive ([#1382](https://github.com/williamboman/mason.nvim/issues/1382)) ([b68d3be](https://github.com/williamboman/mason.nvim/commit/b68d3be4b664671002221d43c82e74a0f1006b26))\n\n## [1.5.0](https://github.com/williamboman/mason.nvim/compare/v1.4.0...v1.5.0) (2023-06-28)\n\n\n### Features\n\n* **command:** add completion for option flags for :MasonInstall ([#1379](https://github.com/williamboman/mason.nvim/issues/1379)) ([e507af7](https://github.com/williamboman/mason.nvim/commit/e507af7b996dae90404345abb2bc88540f931589))\n* **installer:** write more installation output to stdout ([#1376](https://github.com/williamboman/mason.nvim/issues/1376)) ([758ac5b](https://github.com/williamboman/mason.nvim/commit/758ac5b35e823eee74a90f855b2a66afc51ec92d))\n\n\n### Bug Fixes\n\n* **installer:** timeout schema download after 5s ([#1374](https://github.com/williamboman/mason.nvim/issues/1374)) ([d114376](https://github.com/williamboman/mason.nvim/commit/d11437645af60449ff252b2c9abda103c5610520))\n\n## [1.4.0](https://github.com/williamboman/mason.nvim/compare/v1.3.0...v1.4.0) (2023-06-21)\n\n\n### Features\n\n* **fetch:** add explicit default timeout to requests ([#1364](https://github.com/williamboman/mason.nvim/issues/1364)) ([82cae55](https://github.com/williamboman/mason.nvim/commit/82cae550c87466b1163b216bdb9c71cb71dd8f67))\n* **fetch:** include mason.nvim version in User-Agent ([#1362](https://github.com/williamboman/mason.nvim/issues/1362)) ([e706d30](https://github.com/williamboman/mason.nvim/commit/e706d305fbcc8701bd30e31dd727aee2853b9db9))\n\n## [1.3.0](https://github.com/williamboman/mason.nvim/compare/v1.2.1...v1.3.0) (2023-06-18)\n\n\n### Features\n\n* **health:** add advice for Debian/Ubuntu regarding python3 venv ([#1358](https://github.com/williamboman/mason.nvim/issues/1358)) ([6f3853e](https://github.com/williamboman/mason.nvim/commit/6f3853e5ae8c200e29d2e394e479d9c3f8e018f5))\n\n## [1.2.1](https://github.com/williamboman/mason.nvim/compare/v1.2.0...v1.2.1) (2023-06-13)\n\n\n### Bug Fixes\n\n* **providers:** fix some client providers and add some more ([#1354](https://github.com/williamboman/mason.nvim/issues/1354)) ([6f44955](https://github.com/williamboman/mason.nvim/commit/6f4495590a0f9e121b483c9b1236fbabbd80da7a))\n\n## [1.2.0](https://github.com/williamboman/mason.nvim/compare/v1.1.1...v1.2.0) (2023-06-13)\n\n\n### Features\n\n* **command:** improve completion for :MasonInstall ([#1353](https://github.com/williamboman/mason.nvim/issues/1353)) ([13e26c8](https://github.com/williamboman/mason.nvim/commit/13e26c81ff5074ee8f095a791cd37fc1cec37377))\n\n\n### Bug Fixes\n\n* **async:** always check channel state ([#1351](https://github.com/williamboman/mason.nvim/issues/1351)) ([f503346](https://github.com/williamboman/mason.nvim/commit/f5033463bb911a136e577fc6f339328f162e2b4a))\n* **command:** run :MasonUpdate synchronously in headless mode ([#1347](https://github.com/williamboman/mason.nvim/issues/1347)) ([0276793](https://github.com/williamboman/mason.nvim/commit/02767937fc2e1b214c854a8fdde26ae1d3529dd6))\n* **functional:** strip_prefix and strip_suffix should not use patterns ([#1352](https://github.com/williamboman/mason.nvim/issues/1352)) ([f99b702](https://github.com/williamboman/mason.nvim/commit/f99b70233e49db2229350bb82d9ddc6e2f4131c0))\n\n## [1.1.1](https://github.com/williamboman/mason.nvim/compare/v1.1.0...v1.1.1) (2023-05-29)\n\n\n### Bug Fixes\n\n* **ui:** improve search mode UI and remove redundant whitespaces ([#1332](https://github.com/williamboman/mason.nvim/issues/1332)) ([a18c031](https://github.com/williamboman/mason.nvim/commit/a18c031c72a3c7576ba5dc60ee30de8290c8757c))\n\n## [1.1.0](https://github.com/williamboman/mason.nvim/compare/v1.0.1...v1.1.0) (2023-05-18)\n\n\n### Features\n\n* **installer:** lock package installation ([#1290](https://github.com/williamboman/mason.nvim/issues/1290)) ([227f8a9](https://github.com/williamboman/mason.nvim/commit/227f8a9aaae495f481c768f8346edfceaf6d2951))\n* **ui:** add keymap setting for toggling package installation log ([#1268](https://github.com/williamboman/mason.nvim/issues/1268)) ([48bb1cc](https://github.com/williamboman/mason.nvim/commit/48bb1cc33a1fefe94f5ce4972446a1c6ad849f15))\n* **ui:** add search mode ([#1306](https://github.com/williamboman/mason.nvim/issues/1306)) ([3b59f25](https://github.com/williamboman/mason.nvim/commit/3b59f25d435fb1b8d36c4cc26410c3569f0bd795))\n* **ui:** display \"update all\" hint ([#1296](https://github.com/williamboman/mason.nvim/issues/1296)) ([e634134](https://github.com/williamboman/mason.nvim/commit/e634134312bb936f472468a401c9cae6485ab54b))\n\n\n### Bug Fixes\n\n* **sources:** don't skip installation if fixed version is not currently installed ([#1297](https://github.com/williamboman/mason.nvim/issues/1297)) ([9c5edf1](https://github.com/williamboman/mason.nvim/commit/9c5edf13c2e6bd5223eebfeb4557ccc841acaa0e))\n* **ui:** use vim.cmd(\"\") for nvim-0.7.0 compatibility ([#1307](https://github.com/williamboman/mason.nvim/issues/1307)) ([e60b855](https://github.com/williamboman/mason.nvim/commit/e60b855bfa8c7d34387200daa6e54a5e22d3da05))\n\n## [1.0.1](https://github.com/williamboman/mason.nvim/compare/v1.0.0...v1.0.1) (2023-04-26)\n\n\n### Bug Fixes\n\n* **pypi:** also provide install_extra_args to pypi.install ([#1263](https://github.com/williamboman/mason.nvim/issues/1263)) ([646ef07](https://github.com/williamboman/mason.nvim/commit/646ef07907e0960987c13c0b13f69eb808cc66ad))\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "-   [Contribution policy](#contribution-policy)\n-   [Adding a new package](#adding-a-new-package)\n-   [Code style](#code-style)\n-   [Generated code](#generated-code)\n-   [Tests](#tests)\n-   [Adding or changing a feature](#adding-or-changing-a-feature)\n-   [Commit style](#commit-style)\n-   [Pull requests](#pull-requests)\n\n# Contribution policy\n\nThe key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"NOT\nRECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [BCP 14][bcp14],\n[RFC2119][rfc2119], and [RFC8174][rfc8174] when, and only when, they appear in all capitals, as shown here.\n\n[bcp14]: https://tools.ietf.org/html/bcp14\n[rfc2119]: https://tools.ietf.org/html/rfc2119\n[rfc8174]: https://tools.ietf.org/html/rfc8174\n\n# Adding a new package\n\nCore `mason.nvim` package definitions reside within the [`github:mason-org/mason-registry`\nregistry](https://github.com/mason-org/mason-registry/). Contributions to add new packages MUST be done there (refer to\nthe README and existing package definitions).\n\n# Code style\n\nThis project adheres to Editorconfig, Selene, and Stylua code style & formatting rules. New patches MUST adhere to these\ncoding styles.\n\n# Generated code\n\nSome changes such as adding or changing a package definition will require generating some new code. The changes to\ngenerated code MAY be included in a pull request. If it's not included in a pull request, it will automatically be\ngenerated and pushed to your branch before merge.\n\nGenerating code can be done on Unix systems like so:\n\n```sh\nmake generate\n```\n\n# Tests\n\n[Tests](https://github.com/mason-org/mason.nvim/tree/main/tests) MAY be added or modified to reflect any new changes.\nTests can be executed on Unix systems like so:\n\n```sh\nmake test\nFILE=tests/mason-core/managers/luarocks_spec.lua make test\n```\n\n# Adding or changing a feature\n\nAdding or changing a feature MUST be preceded with an issue where scope and acceptance criteria are agreed upon with\nproject maintainers before implementation.\n\n# Commit style\n\nCommits SHOULD follow the [conventional commits guidelines](https://www.conventionalcommits.org/en/v1.0.0/).\n\n# Pull requests\n\nOnce a pull request is marked as ready for review (i.e. not in draft mode), new changes SHOULD NOT be force-pushed to\nthe branch. Merge commits SHOULD be preferred over rebases.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n"
  },
  {
    "path": "Makefile",
    "content": "INSTALL_ROOT_DIR:=$(shell pwd)/tests/fixtures/mason\nNVIM_HEADLESS:=nvim --headless --noplugin -u tests/minimal_init.vim\n\ndependencies:\n\tgit clone --depth 1 https://github.com/nvim-lua/plenary.nvim dependencies/pack/vendor/start/plenary.nvim\n\tgit clone --depth 1 https://github.com/nvim-neotest/neotest dependencies/pack/vendor/start/neotest\n\n.PHONY: clean_dependencies\nclean_dependencies:\n\trm -rf dependencies\n\n.PHONY: clean_fixtures\nclean_fixtures:\n\trm -rf \"${INSTALL_ROOT_DIR}\"\n\n.PHONY: clean\nclean: clean_fixtures clean_dependencies\n\n.PHONY: test\ntest: clean_fixtures dependencies\n\tINSTALL_ROOT_DIR=${INSTALL_ROOT_DIR} $(NVIM_HEADLESS) -c \"call RunTests()\"\n\n# vim:noexpandtab\n"
  },
  {
    "path": "PACKAGES.md",
    "content": "Moved to https://mason-registry.dev/registry/list\n"
  },
  {
    "path": "README.md",
    "content": "![Linux](https://img.shields.io/badge/Linux-%23.svg?logo=linux&color=FCC624&logoColor=black)\n![macOS](https://img.shields.io/badge/macOS-%23.svg?logo=apple&color=000000&logoColor=white)\n![Windows](https://img.shields.io/badge/Windows-%23.svg?logo=windows&color=0078D6&logoColor=white)\n[![GitHub CI](https://github.com/mason-org/mason.nvim/workflows/Tests/badge.svg)](https://github.com/mason-org/mason.nvim/actions?query=workflow%3ATests+branch%3Amain+event%3Apush)\n[![Sponsors](https://img.shields.io/github/sponsors/williamboman)](https://github.com/sponsors/williamboman)\n\n<h1>\n    <img src=\"https://user-images.githubusercontent.com/6705160/177613416-0c0354d2-f431-40d8-87f0-21310f0bba0e.png\" alt=\"mason.nvim\" />\n</h1>\n\n<p align=\"center\">\n    Portable package manager for Neovim that runs everywhere Neovim runs.<br />\n    Easily install and manage LSP servers, DAP servers, linters, and formatters.<br />\n</p>\n<p align=\"center\">\n    <code>:help mason.nvim</code>\n</p>\n<p align=\"center\">\n    <sup>Latest version: v2.2.1</sup> <!-- x-release-please-version -->\n</p>\n\n## Table of Contents\n\n-   [Introduction](#introduction)\n-   [Installation & Usage](#installation--usage)\n    - [Recommended setup for `lazy.nvim`](#recommended-setup-for-lazynvim)\n-   [Requirements](#requirements)\n-   [Commands](#commands)\n-   [Registries](#registries)\n-   [Screenshots](#screenshots)\n-   [Configuration](#configuration)\n\n## Introduction\n\n> [`:h mason-introduction`][help-mason-introduction]\n\n`mason.nvim` is a Neovim plugin that allows you to easily manage external editor tooling such as LSP servers, DAP servers,\nlinters, and formatters through a single interface. It runs everywhere Neovim runs (across Linux, macOS, Windows, etc.),\nwith only a small set of [external requirements](#requirements) needed.\n\nPackages are installed in Neovim's data directory ([`:h standard-path`][help-standard-path]) by default. Executables are\nlinked to a single `bin/` directory, which `mason.nvim` will add to Neovim's PATH during setup, allowing seamless access\nfrom Neovim builtins (LSP client, shell, terminal, etc.) as well as other 3rd party plugins.\n\nFor a list of all available packages, see <https://mason-registry.dev/registry/list>.\n\n## Installation & Usage\n\n> [`:h mason-quickstart`][help-mason-quickstart]\n\nInstall using your plugin manager of choice. **Setup is required**:\n\n```lua\nrequire(\"mason\").setup()\n```\n\n`mason.nvim` is optimized to load as little as possible during setup. Lazy-loading the plugin, or somehow deferring the\nsetup, is not recommended.\n\nRefer to the [Configuration](#configuration) section for information about which settings are available.\n\n### Recommended setup for `lazy.nvim`\n\nThe following is the recommended setup when using `lazy.nvim`. It will set up the plugin for you, meaning **you don't have\nto call `require(\"mason\").setup()` yourself**.\n\n```lua\n{\n    \"mason-org/mason.nvim\",\n    opts = {}\n}\n```\n\n## Requirements\n\n> [`:h mason-requirements`][help-mason-requirements]\n\n`mason.nvim` relaxes the minimum requirements by attempting multiple different utilities (for example, `wget`,\n`curl`, and `Invoke-WebRequest` are all perfect substitutes).\nThe _minimum_ recommended requirements are:\n\n-   neovim `>= 0.10.0`\n-   For Unix systems:\n    -   `git(1)`\n    -   `curl(1)` or `GNU wget(1)`\n    -   `unzip(1)`\n    -   GNU tar (`tar(1)` or `gtar(1)` depending on platform)\n    -   `gzip(1)`\n-   For Windows systems:\n    -   pwsh or powershell\n    -   git\n    -   GNU tar\n    -   One of the following:\n        -   [7zip][7zip]\n        -   [peazip][peazip]\n        -   [archiver][archiver]\n        -   [winzip][winzip]\n        -   [WinRAR][winrar]\n\nNote that `mason.nvim` will regularly shell out to external package managers, such as `cargo` and `npm`. Depending on\nyour personal usage, some of these will also need to be installed. Refer to `:checkhealth mason` for a full list.\n\n[7zip]: https://www.7-zip.org/\n[archiver]: https://github.com/mholt/archiver\n[peazip]: https://peazip.github.io/\n[winzip]: https://www.winzip.com/\n[winrar]: https://www.win-rar.com/\n\n## Commands\n\n> [`:h mason-commands`][help-mason-commands]\n\n-   `:Mason` - opens a graphical status window\n-   `:MasonUpdate` - updates all managed registries\n-   `:MasonInstall <package> ...` - installs/re-installs the provided packages\n-   `:MasonUninstall <package> ...` - uninstalls the provided packages\n-   `:MasonUninstallAll` - uninstalls all packages\n-   `:MasonLog` - opens the `mason.nvim` log file in a new tab window\n\n## Registries\n\nMason's core package registry is located at [mason-org/mason-registry](https://github.com/mason-org/mason-registry).\nBefore any packages can be used, the registry needs to be downloaded. This is done automatically for you when using the\ndifferent Mason commands (e.g. `:MasonInstall`), but can also be done manually by using the `:MasonUpdate` command.\n\nIf you're utilizing Mason's Lua APIs to access packages, it's recommended to use the\n[`:h mason-registry.refresh()`][help-mason-registry-refresh] or [`:h mason-registry.update()`][help-mason-registry-update]\nfunctions to ensure you have the latest package information before retrieving packages.\n\n## Screenshots\n\n|                                                                                                                                                        |                                                                                                                                                  |                                                                                                                                        |\n| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------: |\n|           <img alt=\"Main window\" src=\"https://github.com/user-attachments/assets/b9a57d21-f551-45ad-a1e5-a9fd66291510\">           |                 <img alt=\"Language search\" src=\"https://github.com/user-attachments/assets/3d24fb7b-2c57-4948-923b-0a42bb627cbe\">                 | <img alt=\"Language filter\" src=\"https://github.com/user-attachments/assets/c0ca5818-3c74-4071-bc41-427a2cd1056d\"> |\n| <img alt=\"Package information\" src=\"https://github.com/user-attachments/assets/6f9f6819-ac97-483d-a77c-8f6c6131ac85\"> | <img alt=\"New package versions\" src=\"https://github.com/user-attachments/assets/ff1adc4d-2fcc-46df-ab4c-291c891efa50\"> |   <img alt=\"Help window\" src=\"https://github.com/user-attachments/assets/1fbe75e4-fe69-4417-83e3-82329e1c236e\">   |\n\n## Configuration\n\n> [`:h mason-settings`][help-mason-settings]\n\nYou may optionally configure certain behavior of `mason.nvim` when calling the `.setup()` function. Refer to the\n[default configuration](#default-configuration) for a list of all available settings.\n\nExample:\n\n```lua\nrequire(\"mason\").setup({\n    ui = {\n        icons = {\n            package_installed = \"✓\",\n            package_pending = \"➜\",\n            package_uninstalled = \"✗\"\n        }\n    }\n})\n```\n\n### Configuration using `lazy.nvim`\n\n```lua\n{\n    \"mason-org/mason.nvim\",\n    opts = {\n        ui = {\n            icons = {\n                package_installed = \"✓\",\n                package_pending = \"➜\",\n                package_uninstalled = \"✗\"\n            }\n        }\n    }\n}\n```\n\n### Default configuration\n\n```lua\n---@class MasonSettings\nlocal DEFAULT_SETTINGS = {\n    ---@since 1.0.0\n    -- The directory in which to install packages.\n    install_root_dir = path.concat { vim.fn.stdpath \"data\", \"mason\" },\n\n    ---@since 1.0.0\n    -- Where Mason should put its bin location in your PATH. Can be one of:\n    -- - \"prepend\" (default, Mason's bin location is put first in PATH)\n    -- - \"append\" (Mason's bin location is put at the end of PATH)\n    -- - \"skip\" (doesn't modify PATH)\n    ---@type '\"prepend\"' | '\"append\"' | '\"skip\"'\n    PATH = \"prepend\",\n\n    ---@since 1.0.0\n    -- Controls to which degree logs are written to the log file. It's useful to set this to vim.log.levels.DEBUG when\n    -- debugging issues with package installations.\n    log_level = vim.log.levels.INFO,\n\n    ---@since 1.0.0\n    -- Limit for the maximum amount of packages to be installed at the same time. Once this limit is reached, any further\n    -- packages that are requested to be installed will be put in a queue.\n    max_concurrent_installers = 4,\n\n    ---@since 1.0.0\n    -- [Advanced setting]\n    -- The registries to source packages from. Accepts multiple entries. Should a package with the same name exist in\n    -- multiple registries, the registry listed first will be used.\n    registries = {\n        \"github:mason-org/mason-registry\",\n    },\n\n    ---@since 1.0.0\n    -- The provider implementations to use for resolving supplementary package metadata (e.g., all available versions).\n    -- Accepts multiple entries, where later entries will be used as fallback should prior providers fail.\n    -- Builtin providers are:\n    --   - mason.providers.registry-api  - uses the https://api.mason-registry.dev API\n    --   - mason.providers.client        - uses only client-side tooling to resolve metadata\n    providers = {\n        \"mason.providers.registry-api\",\n        \"mason.providers.client\",\n    },\n\n    github = {\n        ---@since 1.0.0\n        -- The template URL to use when downloading assets from GitHub.\n        -- The placeholders are the following (in order):\n        -- 1. The repository (e.g. \"rust-lang/rust-analyzer\")\n        -- 2. The release version (e.g. \"v0.3.0\")\n        -- 3. The asset name (e.g. \"rust-analyzer-v0.3.0-x86_64-unknown-linux-gnu.tar.gz\")\n        download_url_template = \"https://github.com/%s/releases/download/%s/%s\",\n    },\n\n    pip = {\n        ---@since 1.0.0\n        -- Whether to upgrade pip to the latest version in the virtual environment before installing packages.\n        upgrade_pip = false,\n\n        ---@since 1.0.0\n        -- These args will be added to `pip install` calls. Note that setting extra args might impact intended behavior\n        -- and is not recommended.\n        --\n        -- Example: { \"--proxy\", \"https://proxyserver\" }\n        install_args = {},\n    },\n\n    ui = {\n        ---@since 1.0.0\n        -- Whether to automatically check for new versions when opening the :Mason window.\n        check_outdated_packages_on_open = true,\n\n        ---@since 1.0.0\n        -- The border to use for the UI window. Accepts same border values as |nvim_open_win()|.\n        -- Defaults to `:h 'winborder'` if nil.\n        border = nil,\n\n        ---@since 1.11.0\n        -- The backdrop opacity. 0 is fully opaque, 100 is fully transparent.\n        backdrop = 60,\n\n        ---@since 1.0.0\n        -- Width of the window. Accepts:\n        -- - Integer greater than 1 for fixed width.\n        -- - Float in the range of 0-1 for a percentage of screen width.\n        width = 0.8,\n\n        ---@since 1.0.0\n        -- Height of the window. Accepts:\n        -- - Integer greater than 1 for fixed height.\n        -- - Float in the range of 0-1 for a percentage of screen height.\n        height = 0.9,\n\n        icons = {\n            ---@since 1.0.0\n            -- The list icon to use for installed packages.\n            package_installed = \"◍\",\n            ---@since 1.0.0\n            -- The list icon to use for packages that are installing, or queued for installation.\n            package_pending = \"◍\",\n            ---@since 1.0.0\n            -- The list icon to use for packages that are not installed.\n            package_uninstalled = \"◍\",\n        },\n\n        keymaps = {\n            ---@since 1.0.0\n            -- Keymap to expand a package\n            toggle_package_expand = \"<CR>\",\n            ---@since 1.0.0\n            -- Keymap to install the package under the current cursor position\n            install_package = \"i\",\n            ---@since 1.0.0\n            -- Keymap to reinstall/update the package under the current cursor position\n            update_package = \"u\",\n            ---@since 1.0.0\n            -- Keymap to check for new version for the package under the current cursor position\n            check_package_version = \"c\",\n            ---@since 1.0.0\n            -- Keymap to update all installed packages\n            update_all_packages = \"U\",\n            ---@since 1.0.0\n            -- Keymap to check which installed packages are outdated\n            check_outdated_packages = \"C\",\n            ---@since 1.0.0\n            -- Keymap to uninstall a package\n            uninstall_package = \"X\",\n            ---@since 1.0.0\n            -- Keymap to cancel a package installation\n            cancel_installation = \"<C-c>\",\n            ---@since 1.0.0\n            -- Keymap to apply language filter\n            apply_language_filter = \"<C-f>\",\n            ---@since 1.1.0\n            -- Keymap to toggle viewing package installation log\n            toggle_package_install_log = \"<CR>\",\n            ---@since 1.8.0\n            -- Keymap to toggle the help view\n            toggle_help = \"g?\",\n        },\n    },\n}\n```\n\n---\n\n<sup>\n👋 didn't find what you were looking for? Try looking in the <a href=\"./doc/mason.txt\">help docs</a> <code>:help mason.nvim</code>!\n</sup>\n\n[help-mason-commands]: ./doc/mason.txt#L140\n[help-mason-introduction]: ./doc/mason.txt#L11\n[help-mason-quickstart]: ./doc/mason.txt#L42\n[help-mason-registry-refresh]: ./doc/mason.txt#L520\n[help-mason-registry-update]: ./doc/mason.txt#L513\n[help-mason-requirements]: ./doc/mason.txt#L25\n[help-mason-settings]: ./doc/mason.txt#L200\n[help-standard-path]: https://neovim.io/doc/user/starting.html#standard-path\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security policy\n\n## Reporting a Vulnerability\n\nPlease report any suspected security vulnerabilities [here][new-advisory]. If the issue is confirmed, we will release a\npatch as soon as possible depending on complexity. Please follow [responsible disclosure\npractices](https://en.wikipedia.org/wiki/Coordinated_vulnerability_disclosure). Thanks!\n\n[new-advisory]: https://github.com/mason-org/mason.nvim/security/advisories/new\n"
  },
  {
    "path": "doc/.gitignore",
    "content": "tags\n"
  },
  {
    "path": "doc/mason.txt",
    "content": "*mason.nvim*\n\nMinimum version of neovim: 0.10.0\n\nAuthor: William Boman\n                                                            *mason-help-guide*\n                                       Type |gO| to see the table of contents.\n                        Press |K| on a helptag to jump to it: |mason-help-guide|\n\n==============================================================================\nINTRODUCTION                                              *mason-introduction*\n\n`mason.nvim` is a Neovim plugin that allows you to easily manage external\neditor tooling such as LSP servers, DAP servers, linters, and formatters\nthrough a single interface. It runs everywhere Neovim runs (across Linux,\nmacOS, Windows, etc.), with only a small set of external requirements needed.\n\nPackages are installed in Neovim's data directory (`:h standard-path`) by\ndefault. Executables are linked to a single `bin/` directory, which\n`mason.nvim` will add to Neovim's PATH during setup, allowing seamless access\nfrom Neovim builtins (shell, terminal, etc.) as well as other 3rd party\nplugins.\n\n==============================================================================\nREQUIREMENTS                                              *mason-requirements*\n\n`mason.nvim` relaxes the minimum requirements by attempting multiple different\nutilities (for example, `wget`, `curl`, and `Invoke-WebRequest` are all\nperfect substitutes). The _minimum_ recommended requirements are:\n\n-   neovim `>= 0.10.0`\n-   For Unix systems: `git(1)`, `curl(1)` or `wget(1)`, `unzip(1)`, `tar(1)`,\n    `gzip(1)`\n-   For Windows systems: pwsh or powershell, git, tar, and 7zip or peazip or\n    archiver or winzip or WinRAR\n\nNote that Mason will regularly shell out to external package managers,\nsuch as `cargo` and `npm`. Depending on your personal usage, some of these\nwill also need to be installed. Refer to `:checkhealth mason` for a full list.\n\n==============================================================================\nQUICK START                                                 *mason-quickstart*\n\n-----------------\nSETTING UP MASON.NVIM\n\nFirst you'll need to set up Mason. This is done by calling the `setup()`\nfunction:\n>lua\n    require(\"mason\").setup()\n<\nMason will do the following during setup:\n    1) add Mason's `bin/` directory to the Neovim session's PATH\n    2) register commands (|mason-commands|)\n\nRefer to |mason-settings| for all available settings.\n\n-----------------\nINSTALLING PACKAGES\n\nInstall a package via |:MasonInstall|, for example:\n>vim\n    :MasonInstall stylua\n<\nYou may also install multiple packages at a time:\n>vim\n    :MasonInstall stylua lua-language-server\n<\nTo install a specific version of a package, you may provide it as part of the\npackage name, like so:\n>vim\n    :MasonInstall rust-analyzer@nightly\n<\nPlease refer to each package's own release pages to find which versions are\navailable.\n\nYou may also install packages in headless mode. This will run the command in\nblocking mode and the command won't yield back until all packages have\nfinished installing:\n>sh\n    $ nvim --headless -c \"MasonInstall lua-language-server rust-analyzer\" -c qall\n<\nNote: ~\n    You may also use Mason's Lua API to programmatically manage package\n    installations. Through this interface you will also gain access to more\n    features to allow further customization.\n\n-----------------\nTHE MASON WINDOW\n\nTo view the UI for mason, run: >vim\n    :Mason\n<\n\nThrough this UI you may explore which packages that are available, see which\ninstalled packages have new versions available, install, uninstall, or update\npackages, expand package information, and more. The UI comes with a set of\nkeybinds which you may find in the help view by pressing `g?` when the Mason\nwindow is open.\n\n==============================================================================\nREGISTRIES                                                  *mason-registries*\n\n`mason.nvim` sources package definitions from the registries it has been\nconfigured with (see |mason-settings|). `mason.nvim` uses the core registry,\ngoverned by `mason.nvim`, by default. This may be extended, or even entirely\noverridden, through additional registries, like so:\n\n>lua\n    require(\"mason\").setup {\n        registries = {\n            \"lua:my-registry\",\n            \"github:mason-org/mason-registry\",\n        },\n    }\n<\n\nPackages are loaded from registries in the order they've been configured,\nwith registries appearing first in the list having precedence.\n\n==============================================================================\nHOW TO INSTALL PACKAGES                        *mason-how-to-install-packages*\n\nYou may install packages either via the command interface or via Mason's Lua\nAPIs. See |:MasonInstall| for more details.\n\n==============================================================================\nHOW TO USE PACKAGES                                *mason-how-to-use-packages*\n\nAlthough many packages are perfectly usable out of the box through Neovim\nbuiltins, it is recommended to use other 3rd party plugins to further\nintegrate these.\n\nSee also ~\n    Execute external commands: |:!cmd|.\n    Launch an embedded terminal: |terminal|.\n    Launch background jobs: |jobstart| & |uv.spawn()| (via |vim.loop|)\n\n==============================================================================\nCOMMANDS                                                      *mason-commands*\n\n------------------------------------------------------------------------------\nOPEN THE MASON WINDOW                                                 *:Mason*\n\n:Mason\n\nOpens the graphical status window.\n\nThrough this UI you may explore which packages that are available, see which\ninstalled packages have new versions available, install, uninstall, or update\npackages, expand package information, and more. The UI comes with a set of\nkeybinds which you may find in the help view by pressing `g?` when the Mason\nwindow is open.\n\n------------------------------------------------------------------------------\nUPDATE REGISTRIES                                               *:MasonUpdate*\n>vim\n:MasonUpdate\n<\nUpdates all managed registries.\n\n------------------------------------------------------------------------------\nINSTALLING PACKAGES                                            *:MasonInstall*\n>vim\n:MasonInstall <package> ...\n<\nInstalls the provided packages. Packages may include a version specifier,\nlike so:\n>vim\n    :MasonInstall lua-language-server@v3.0.0\n<\nRuns in blocking fashion if there are no UIs attached (i.e. running in\nheadless mode):\n>sh\n    $ nvim --headless -c \"MasonInstall stylua\" -c \"qall\"\n<\n------------------------------------------------------------------------------\nUNINSTALLING PACKAGES                                        *:MasonUninstall*\n>vim\n:MasonUninstall <package> ...\n<\n\nUninstalls the provided packages.\n\n------------------------------------------------------------------------------\nUNINSTALLING ALL PACKAGES                                 *:MasonUninstallAll*\n>vim\n:MasonUninstallAll\n<\nUninstalls all installed packages.\n\n------------------------------------------------------------------------------\nVIEW THE MASON LOG                                                 *:MasonLog*\n>vim\n:MasonLog\n<\nOpens the log file in a new tab window.\n\n==============================================================================\nSETTINGS                                                      *mason-settings*\n\nYou can configure certain behavior of mason when calling the `.setup()`\nfunction.\n\nRefer to the |mason-default-settings| for all available settings.\n\nExample:\n>lua\n    require(\"mason\").setup({\n        ui = {\n            icons = {\n                package_installed = \"✓\",\n                package_pending = \"➜\",\n                package_uninstalled = \"✗\"\n            }\n        }\n    })\n<\n                                                      *mason-default-settings*\n>lua\n    ---@class MasonSettings\n    local DEFAULT_SETTINGS = {\n        ---@since 1.0.0\n        -- The directory in which to install packages.\n        install_root_dir = path.concat { vim.fn.stdpath \"data\", \"mason\" },\n\n        ---@since 1.0.0\n        -- Where Mason should put its bin location in your PATH. Can be one of:\n        -- - \"prepend\" (default, Mason's bin location is put first in PATH)\n        -- - \"append\" (Mason's bin location is put at the end of PATH)\n        -- - \"skip\" (doesn't modify PATH)\n        ---@type '\"prepend\"' | '\"append\"' | '\"skip\"'\n        PATH = \"prepend\",\n\n        ---@since 1.0.0\n        -- Controls to which degree logs are written to the log file. It's useful to set this to vim.log.levels.DEBUG when\n        -- debugging issues with package installations.\n        log_level = vim.log.levels.INFO,\n\n        ---@since 1.0.0\n        -- Limit for the maximum amount of packages to be installed at the same time. Once this limit is reached, any further\n        -- packages that are requested to be installed will be put in a queue.\n        max_concurrent_installers = 4,\n\n        ---@since 1.0.0\n        -- [Advanced setting]\n        -- The registries to source packages from. Accepts multiple entries. Should a package with the same name exist in\n        -- multiple registries, the registry listed first will be used.\n        registries = {\n            \"github:mason-org/mason-registry\",\n        },\n\n        ---@since 1.0.0\n        -- The provider implementations to use for resolving supplementary package metadata (e.g., all available versions).\n        -- Accepts multiple entries, where later entries will be used as fallback should prior providers fail.\n        -- Builtin providers are:\n        --   - mason.providers.registry-api  - uses the https://api.mason-registry.dev API\n        --   - mason.providers.client        - uses only client-side tooling to resolve metadata\n        providers = {\n            \"mason.providers.registry-api\",\n            \"mason.providers.client\",\n        },\n\n        github = {\n            ---@since 1.0.0\n            -- The template URL to use when downloading assets from GitHub.\n            -- The placeholders are the following (in order):\n            -- 1. The repository (e.g. \"rust-lang/rust-analyzer\")\n            -- 2. The release version (e.g. \"v0.3.0\")\n            -- 3. The asset name (e.g. \"rust-analyzer-v0.3.0-x86_64-unknown-linux-gnu.tar.gz\")\n            download_url_template = \"https://github.com/%s/releases/download/%s/%s\",\n        },\n\n        pip = {\n            ---@since 1.0.0\n            -- Whether to upgrade pip to the latest version in the virtual environment before installing packages.\n            upgrade_pip = false,\n\n            ---@since 1.0.0\n            -- These args will be added to `pip install` calls. Note that setting extra args might impact intended behavior\n            -- and is not recommended.\n            --\n            -- Example: { \"--proxy\", \"https://proxyserver\" }\n            install_args = {},\n        },\n\n        ui = {\n            ---@since 1.0.0\n            -- Whether to automatically check for new versions when opening the :Mason window.\n            check_outdated_packages_on_open = true,\n\n            ---@since 1.0.0\n            -- The border to use for the UI window. Accepts same border values as |nvim_open_win()|.\n            -- Defaults to `:h 'winborder'` if nil.\n            border = nil,\n\n            ---@since 1.11.0\n            -- The backdrop opacity. 0 is fully opaque, 100 is fully transparent.\n            backdrop = 60,\n\n            ---@since 1.0.0\n            -- Width of the window. Accepts:\n            -- - Integer greater than 1 for fixed width.\n            -- - Float in the range of 0-1 for a percentage of screen width.\n            width = 0.8,\n\n            ---@since 1.0.0\n            -- Height of the window. Accepts:\n            -- - Integer greater than 1 for fixed height.\n            -- - Float in the range of 0-1 for a percentage of screen height.\n            height = 0.9,\n\n            icons = {\n                ---@since 1.0.0\n                -- The list icon to use for installed packages.\n                package_installed = \"◍\",\n                ---@since 1.0.0\n                -- The list icon to use for packages that are installing, or queued for installation.\n                package_pending = \"◍\",\n                ---@since 1.0.0\n                -- The list icon to use for packages that are not installed.\n                package_uninstalled = \"◍\",\n            },\n\n            keymaps = {\n                ---@since 1.0.0\n                -- Keymap to expand a package\n                toggle_package_expand = \"<CR>\",\n                ---@since 1.0.0\n                -- Keymap to install the package under the current cursor position\n                install_package = \"i\",\n                ---@since 1.0.0\n                -- Keymap to reinstall/update the package under the current cursor position\n                update_package = \"u\",\n                ---@since 1.0.0\n                -- Keymap to check for new version for the package under the current cursor position\n                check_package_version = \"c\",\n                ---@since 1.0.0\n                -- Keymap to update all installed packages\n                update_all_packages = \"U\",\n                ---@since 1.0.0\n                -- Keymap to check which installed packages are outdated\n                check_outdated_packages = \"C\",\n                ---@since 1.0.0\n                -- Keymap to uninstall a package\n                uninstall_package = \"X\",\n                ---@since 1.0.0\n                -- Keymap to cancel a package installation\n                cancel_installation = \"<C-c>\",\n                ---@since 1.0.0\n                -- Keymap to apply language filter\n                apply_language_filter = \"<C-f>\",\n                ---@since 1.1.0\n                -- Keymap to toggle viewing package installation log\n                toggle_package_install_log = \"<CR>\",\n                ---@since 1.8.0\n                -- Keymap to toggle the help view\n                toggle_help = \"g?\",\n            },\n        },\n    }\n<\n\n==============================================================================\nDOWNLOAD MIRRORS                                      *mason-download-mirrors*\n\n------------------------------------------------------------------------------\nGITHUB MIRROR                                   *mason-download-mirror-github*\n\n    It's possible to customize the download URL used when downloading assets\n    from GitHub releases by setting the `github.download_url_template`\n    settings during setup, like so:\n>lua\n    require(\"mason\").setup {\n        github = {\n            -- The template URL to use when downloading assets from GitHub.\n            -- The placeholders are the following (in order):\n            -- 1. The repository (e.g. \"rust-lang/rust-analyzer\")\n            -- 2. The release version (e.g. \"v0.3.0\")\n            -- 3. The asset name (e.g. \"rust-analyzer-v0.3.0-x86_64-unknown-linux-gnu.tar.gz\")\n            download_url_template = \"https://my.mirror.com/%s/releases/download/%s/%s\",\n        },\n    }\n<\n\n==============================================================================\nINSTALLATION ERRORS                                             *mason-errors*\n\n                                                       *mason-provider-errors*\nBy default, Mason uses the api.mason-registry.dev API to resolve package\nmetadata. Calling this service may result in network errors on some networks\n(e.g., SSL issues on corporate VPNs). If resolving the SSL error is not an\noption, you will have to change the provider implementation. Mason provides a\nclient provider which calls underlying 3rd party service APIs directly, which\nyou can enable like so:\n>lua\n    require(\"mason\").setup {\n        providers = {\n            \"mason.providers.client\",\n            \"mason.providers.registry-api\",\n        }\n    }\n<\n    Note: ~\n        The client provider have less overall coverage and may come with\n        additional performance penalties (spawning slow commands, network &\n        parsing overheads, etc.).\n\n==============================================================================\nDEBUGGING                                                    *mason-debugging*\n\nTo help with debugging issues with installing/uninstalling packages, please\nmake sure to set mason's log level to DEBUG or TRACE, like so:\n>lua\n    require(\"mason\").setup {\n        log_level = vim.log.levels.DEBUG\n    }\n<\nYou may find the logs by entering the command `:MasonLog`. Providing the\ncontents of this file when reporting an issue will help tremendously. Remember\nto redo whatever is failing after changing the log level in order to capture\nnew log entries.\n\n==============================================================================\nLua module: \"mason\"\n>lua\n    require(\"mason\")\n<\n                                                               *mason.setup()*\nsetup({config})\n    Sets up mason with the provided {config} (see |mason-settings|).\n\n==============================================================================\nLua module: \"mason-registry\"\n>lua\n    require(\"mason-registry\")\n<\n                                               *mason-registry.is_installed()*\nis_installed({package_name})\n    Checks whether the provided package name is installed. In many situations,\n    this is a more efficient option than the Package:is_installed() method due\n    to a smaller amount of modules required to load.\n\n    Parameters:\n        {package_name} - string\n\n    Returns:\n        boolean\n\n                                                *mason-registry.get_package()*\nget_package({package_name})\n    Returns an instance of the Package class if the provided package name\n    exists.\n\n    This function errors if a package cannot be found.\n\n    Parameters:\n        {package_name} - string\n\n    Returns:\n        Package\n\n                                                *mason-registry.has_package()*\nhas_package({package_name})\n    Returns true if the provided package_name can be found in the registry.\n\n    Parameters:\n        {package_name} - string\n\n    Returns:\n        boolean\n\n                                     *mason-registry.get_installed_packages()*\nget_installed_packages()\n    Returns all installed package instances. This is a slower function that\n    loads more modules.\n\n    Returns:\n        Package[]\n\n                                *mason-registry.get_installed_package_names()*\nget_installed_package_names()\n    Returns all installed package names. This is a fast function that doesn't\n    load any extra modules.\n\n    Returns:\n        string[]\n\n                                           *mason-registry.get_all_packages()*\nget_all_packages()\n    Returns all package instances. This is a slower function that loads more\n    modules.\n\n    Returns:\n        Package[]\n\n                                      *mason-registry.get_all_package_names()*\nget_all_package_names()\n    Returns all package names. This is a faster function than\n    |mason-registry.get_all_packages()| because it loads fewer modules.\n\n    Returns:\n        string[]\n\n                                      *mason-registry.get_all_package_specs()*\nget_all_package_specs()\n    Returns all package specifications. This is a faster function than\n    |mason-registry.get_all_packages()| because it loads fewer modules.\n\n    Returns:\n        RegistryPackageSpec[]\n\n                                                     *mason-registry.update()*\nupdate({callback})\n    Updates all managed registries.\n\n    Parameters:\n        {callback} - Callback of the signature `fun(success: boolean, updated_registries: RegistrySource[])`\n\n                                                    *mason-registry.refresh()*\nrefresh({callback?})\n    Refreshes all registries if needed. This is a convenience wrapper around\n    |mason-registry.update()| that only updates registries if:\n        1) registries haven't been updated in a while\n        2) or, one or more registries are not installed\n\n    Runs in a blocking fashion if no {callback} is provided. Note that when\n    running in blocking fashion the entire editor is frozen, so prefer the\n    asynchronous variant unless absolutely needed.\n\n    Parameters:\n        {callback?} (optional) - Invoked when the registry has been refreshed.\n\n    Example:\n>lua\n        local registry = require(\"mason-registry\")\n\n        -- 1. synchronous\n        registry.refresh()\n        local packages = registry.get_all_packages()\n        ...\n\n        -- 2. asynchronous\n        registry.refresh(function ()\n            local packages = registry.get_all_packages()\n            ...\n        end)\n<\n\n\nvim:tw=78:ft=help:norl:expandtab:sw=4\n"
  },
  {
    "path": "lua/mason/api/command.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal platform = require \"mason-core.platform\"\n\nlocal function Mason()\n    require(\"mason.ui\").open()\nend\n\nvim.api.nvim_create_user_command(\"Mason\", Mason, {\n    desc = \"Opens mason's UI window.\",\n    nargs = 0,\n})\n\nlocal get_valid_packages = _.filter_map(function(pkg_specifier)\n    local Optional = require \"mason-core.optional\"\n    local notify = require \"mason-core.notify\"\n    local Package = require \"mason-core.package\"\n    local registry = require \"mason-registry\"\n    local package_name, version = Package.Parse(pkg_specifier)\n    local ok, pkg = pcall(registry.get_package, package_name)\n    if ok and pkg then\n        return Optional.of { pkg = pkg, version = version }\n    else\n        notify((\"%q is not a valid package.\"):format(pkg_specifier), vim.log.levels.ERROR)\n        return Optional.empty()\n    end\nend)\n\n---@param package_specifiers string[]\n---@param opts? table<string, string | boolean>\nlocal function MasonInstall(package_specifiers, opts)\n    opts = opts or {}\n    local a = require \"mason-core.async\"\n    local registry = require \"mason-registry\"\n    local Optional = require \"mason-core.optional\"\n\n    local install_packages = _.filter_map(function(target)\n        if target.pkg:is_installing() then\n            return Optional.empty()\n        else\n            return Optional.of(target.pkg:install {\n                version = target.version,\n                debug = opts.debug,\n                force = opts.force,\n                strict = opts.strict,\n                target = opts.target,\n            })\n        end\n    end)\n\n    if platform.is_headless then\n        registry.refresh()\n        local valid_packages = get_valid_packages(package_specifiers)\n        if #valid_packages ~= #package_specifiers then\n            -- When executing in headless mode we don't allow any of the provided packages to be invalid.\n            -- This is to avoid things like scripts silently not erroring even if they've provided one or more invalid packages.\n            return vim.cmd [[1cq]]\n        end\n        a.run_blocking(function()\n            local results = {\n                a.wait_all(_.map(\n                    ---@param target { pkg: Package, version: string? }\n                    function(target)\n                        return function()\n                            if target.pkg:is_installing() then\n                                return\n                            end\n                            return a.wait(function(resolve)\n                                local handle = target.pkg:install({\n                                    version = target.version,\n                                    debug = opts.debug,\n                                    force = opts.force,\n                                    strict = opts.strict,\n                                    target = opts.target,\n                                }, function(success, err)\n                                    resolve { success, target.pkg, err }\n                                end)\n                                if not opts.quiet then\n                                    handle\n                                        :on(\"stdout\", vim.schedule_wrap(vim.api.nvim_out_write))\n                                        :on(\"stderr\", vim.schedule_wrap(vim.api.nvim_err_write))\n                                end\n                            end)\n                        end\n                    end,\n                    valid_packages\n                )),\n            }\n            a.scheduler()\n\n            local is_failure = _.compose(_.equals(false), _.head)\n            if _.any(is_failure, results) then\n                local failures = _.filter(is_failure, results)\n                local failed_packages = _.map(_.nth(2), failures)\n                for _, failure in ipairs(failures) do\n                    local _, pkg, error = unpack(failure)\n                    vim.api.nvim_err_writeln((\"Package %s failed with the following error:\"):format(pkg.name))\n                    vim.api.nvim_err_writeln(tostring(error))\n                end\n                vim.cmd [[1cq]]\n            end\n        end)\n    else\n        local ui = require \"mason.ui\"\n        ui.open()\n        -- Important: We start installation of packages _after_ opening the UI. This gives the UI components a chance to\n        -- register the necessary event handlers in time, avoiding desynced state.\n        registry.refresh(function()\n            local valid_packages = get_valid_packages(package_specifiers)\n            install_packages(valid_packages)\n            vim.schedule(function()\n                ui.set_sticky_cursor \"installing-section\"\n            end)\n        end)\n    end\nend\n\nlocal parse_opts = _.compose(\n    _.from_pairs,\n    _.map(_.compose(function(arg)\n        if #arg == 2 then\n            return arg\n        else\n            return { arg[1], true }\n        end\n    end, _.split \"=\", _.gsub(\"^%-%-\", \"\")))\n)\n\n---@param args string[]\n---@return table<string, true|string> opts, string[] args\nlocal function parse_args(args)\n    local opts_list, args = unpack(_.partition(_.starts_with \"--\", args))\n    local opts = parse_opts(opts_list)\n    return opts, args\nend\n\nvim.api.nvim_create_user_command(\"MasonInstall\", function(opts)\n    local command_opts, packages = parse_args(opts.fargs)\n    MasonInstall(packages, command_opts)\nend, {\n    desc = \"Install one or more packages.\",\n    nargs = \"+\",\n    ---@param arg_lead string\n    complete = function(arg_lead)\n        local registry = require \"mason-registry\"\n        registry.refresh()\n        if _.starts_with(\"--\", arg_lead) then\n            return _.filter(_.starts_with(arg_lead), {\n                \"--debug\",\n                \"--force\",\n                \"--strict\",\n                \"--target=\",\n            })\n        elseif _.matches(\"^.+@\", arg_lead) then\n            local pkg_name, version = unpack(_.match(\"^(.+)@(.*)\", arg_lead))\n            local ok, pkg = pcall(registry.get_package, pkg_name)\n            if not ok or not pkg then\n                return {}\n            end\n            local a = require \"mason-core.async\"\n            return a.run_blocking(function()\n                return a.wait_first {\n                    function()\n                        return pkg:get_all_versions()\n                            :map(\n                                _.compose(\n                                    _.map(_.concat(arg_lead)),\n                                    _.map(_.strip_prefix(version)),\n                                    _.filter(_.starts_with(version))\n                                )\n                            )\n                            :get_or_else {}\n                    end,\n                    function()\n                        a.sleep(4000)\n                        return {}\n                    end,\n                }\n            end)\n        end\n\n        local all_pkg_names = registry.get_all_package_names()\n        return _.sort_by(_.identity, _.filter(_.starts_with(arg_lead), all_pkg_names))\n    end,\n})\n\n---@param package_names string[]\nlocal function MasonUninstall(package_names)\n    local valid_packages = get_valid_packages(package_names)\n    if #valid_packages > 0 then\n        _.each(function(target)\n            target.pkg:uninstall()\n        end, valid_packages)\n        require(\"mason.ui\").open()\n    end\nend\n\nvim.api.nvim_create_user_command(\"MasonUninstall\", function(opts)\n    MasonUninstall(opts.fargs)\nend, {\n    desc = \"Uninstall one or more packages.\",\n    nargs = \"+\",\n    ---@param arg_lead string\n    complete = function(arg_lead)\n        local registry = require \"mason-registry\"\n        return _.sort_by(_.identity, _.filter(_.starts_with(arg_lead), registry.get_installed_package_names()))\n    end,\n})\n\nlocal function MasonUninstallAll()\n    local registry = require \"mason-registry\"\n    require(\"mason.ui\").open()\n    for _, pkg in ipairs(registry.get_installed_packages()) do\n        pkg:uninstall()\n    end\nend\n\nvim.api.nvim_create_user_command(\"MasonUninstallAll\", MasonUninstallAll, {\n    desc = \"Uninstall all packages.\",\n})\n\nlocal function MasonUpdate()\n    local notify = require \"mason-core.notify\"\n    local registry = require \"mason-registry\"\n    notify \"Updating registries…\"\n\n    ---@param success boolean\n    ---@param updated_registries RegistrySource[]\n    local function handle_result(success, updated_registries)\n        if success then\n            local count = #updated_registries\n            notify((\"Successfully updated %d %s.\"):format(count, count == 1 and \"registry\" or \"registries\"))\n        else\n            notify((\"Failed to update registries: %s\"):format(updated_registries), vim.log.levels.ERROR)\n        end\n    end\n\n    if platform.is_headless then\n        local a = require \"mason-core.async\"\n        a.run_blocking(function()\n            local success, updated_registries = a.wait(registry.update)\n            a.scheduler()\n            handle_result(success, updated_registries)\n        end)\n    else\n        registry.update(_.scheduler_wrap(handle_result))\n    end\nend\n\nvim.api.nvim_create_user_command(\"MasonUpdate\", MasonUpdate, {\n    desc = \"Update Mason registries.\",\n})\n\nlocal function MasonLog()\n    local log = require \"mason-core.log\"\n    vim.cmd(([[tabnew %s]]):format(log.outfile))\nend\n\nvim.api.nvim_create_user_command(\"MasonLog\", MasonLog, {\n    desc = \"Opens the mason.nvim log.\",\n})\n\nreturn {\n    Mason = Mason,\n    MasonInstall = MasonInstall,\n    MasonUninstall = MasonUninstall,\n    MasonUninstallAll = MasonUninstallAll,\n    MasonUpdate = MasonUpdate,\n    MasonLog = MasonLog,\n}\n"
  },
  {
    "path": "lua/mason/health.lua",
    "content": "local health = vim.health or require \"health\"\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal control = require \"mason-core.async.control\"\nlocal platform = require \"mason-core.platform\"\nlocal providers = require \"mason-core.providers\"\nlocal registry = require \"mason-registry\"\nlocal settings = require \"mason.settings\"\nlocal spawn = require \"mason-core.spawn\"\nlocal version = require \"mason.version\"\n\nlocal Semaphore = control.Semaphore\n\nlocal M = {}\n\nlocal report_start = _.scheduler_wrap(health.start or health.report_start)\nlocal report_ok = _.scheduler_wrap(health.ok or health.report_ok)\nlocal report_warn = _.scheduler_wrap(health.warn or health.report_warn)\nlocal report_error = _.scheduler_wrap(health.error or health.report_error)\n\nlocal sem = Semaphore:new(5)\n\n---@async\n---@param opts {cmd:string, args:string[], name: string, use_stderr: boolean?, version_check: (fun(version: string): string?), relaxed: boolean?, advice: string[]}\nlocal function check(opts)\n    local get_first_non_empty_line = _.compose(_.head, _.filter(_.complement(_.matches \"^%s*$\")), _.split \"\\n\")\n\n    local permit = sem:acquire()\n\n    Result.try(function(try)\n        local result = try(spawn[opts.cmd] {\n            opts.args,\n            on_spawn = function(_, stdio)\n                local stdin = stdio[1]\n                -- some processes (`sh` for example) will endlessly read from stdin, so we close it immediately\n                if not stdin:is_closing() then\n                    stdin:close()\n                end\n            end,\n        })\n\n        ---@type string?\n        local version = get_first_non_empty_line(opts.use_stderr and result.stderr or result.stdout)\n\n        if opts.version_check then\n            local ok, version_mismatch = pcall(opts.version_check, version)\n            if ok and version_mismatch then\n                local report = opts.relaxed and report_warn or report_error\n                report((\"%s: unsupported version `%s`\"):format(opts.name, version), { version_mismatch })\n                return\n            elseif not ok then\n                local report = opts.relaxed and report_warn or report_error\n                report((\"%s: failed to parse version\"):format(opts.name), { (\"Error: %s\"):format(version_mismatch) })\n                return\n            end\n        end\n\n        report_ok((\"%s: `%s`\"):format(opts.name, version or \"Ok\"))\n    end):on_failure(function(err)\n        local report = opts.relaxed and report_warn or report_error\n        report((\"%s: not available\"):format(opts.name), opts.advice or { tostring(err) })\n    end)\n    permit:forget()\nend\n\nlocal function check_registries()\n    report_start \"mason.nvim [Registries]\"\n    a.wait(registry.refresh)\n    for source in registry.sources:iterate { include_uninstalled = true } do\n        if source:is_installed() then\n            report_ok((\"Registry `%s` is installed.\"):format(source:get_display_name()))\n        else\n            report_error(\n                (\"Registry `%s` is not installed.\"):format(source:get_display_name()),\n                { \"Run :MasonUpdate to install.\" }\n            )\n        end\n    end\nend\n\nlocal function check_neovim()\n    if vim.fn.has \"nvim-0.10.0\" == 1 then\n        report_ok \"neovim version >= 0.10.0\"\n    else\n        report_error(\"neovim version < 0.10.0\", { \"Upgrade Neovim.\" })\n    end\nend\n\n---@async\nlocal function check_core_utils()\n    report_start \"mason.nvim [Core utils]\"\n\n    check { name = \"unzip\", cmd = \"unzip\", args = { \"-v\" }, relaxed = true }\n\n    -- wget is used interchangeably with curl, but with lower priority, so we mark wget as relaxed\n    check { cmd = \"wget\", args = { \"--help\" }, name = \"wget\", relaxed = true }\n    check { cmd = \"curl\", args = { \"--version\" }, name = \"curl\" }\n    check {\n        cmd = \"gzip\",\n        args = { \"--version\" },\n        name = \"gzip\",\n        use_stderr = platform.is.mac, -- Apple gzip prints version string to stderr\n        relaxed = platform.is.win,\n    }\n\n    a.scheduler()\n    local tar = vim.fn.executable \"gtar\" == 1 and \"gtar\" or \"tar\"\n    check { cmd = tar, args = { \"--version\" }, name = tar }\n\n    if platform.is.unix then\n        check { cmd = \"bash\", args = { \"--version\" }, name = \"bash\" }\n    end\n\n    if platform.is.win then\n        check {\n            cmd = \"pwsh\",\n            args = {\n                \"-NoProfile\",\n                \"-Command\",\n                [[$PSVersionTable.PSVersion, $PSVersionTable.OS, $PSVersionTable.Platform -join \" \"]],\n            },\n            name = \"pwsh\",\n        }\n        check { cmd = \"7z\", args = { \"--help\" }, name = \"7z\", relaxed = true }\n    end\nend\n\nlocal function check_thunk(opts)\n    return function()\n        check(opts)\n    end\nend\n\n---@async\nlocal function check_languages()\n    report_start \"mason.nvim [Languages]\"\n\n    a.wait_all {\n        check_thunk {\n            cmd = \"go\",\n            args = { \"version\" },\n            name = \"Go\",\n            relaxed = true,\n            version_check = function(version)\n                -- Parses output such as \"go version go1.17.3 darwin/arm64\" into major, minor, patch components\n                local _, _, major, minor = version:find \"go(%d+)%.(%d+)\"\n                -- Due to https://go.dev/doc/go-get-install-deprecation\n                if not (tonumber(major) >= 1 and tonumber(minor) >= 17) then\n                    return \"Go version must be >= 1.17.\"\n                end\n            end,\n        },\n        check_thunk {\n            cmd = \"cargo\",\n            args = { \"--version\" },\n            name = \"cargo\",\n            relaxed = true,\n            version_check = function(version)\n                local _, _, major, minor = version:find \"(%d+)%.(%d+)%.(%d+)\"\n                if (tonumber(major) <= 1) and (tonumber(minor) < 60) then\n                    return \"Some cargo installations require Rust >= 1.60.0.\"\n                end\n            end,\n        },\n        check_thunk {\n            cmd = \"luarocks\",\n            args = { \"--version\" },\n            name = \"luarocks\",\n            relaxed = true,\n            version_check = function(version)\n                local _, _, major = version:find \"(%d+)%.(%d+)%.(%d+)\"\n                if not (tonumber(major) >= 3) then\n                    -- Because of usage of \"--dev\" flag\n                    return \"Luarocks version must be >= 3.0.0.\"\n                end\n            end,\n        },\n        check_thunk { cmd = \"ruby\", args = { \"--version\" }, name = \"Ruby\", relaxed = true },\n        check_thunk { cmd = \"gem\", args = { \"--version\" }, name = \"RubyGem\", relaxed = true },\n        check_thunk { cmd = \"composer\", args = { \"--version\" }, name = \"Composer\", relaxed = true },\n        check_thunk { cmd = \"php\", args = { \"--version\" }, name = \"PHP\", relaxed = true },\n        check_thunk {\n            cmd = \"npm\",\n            args = { \"--version\" },\n            name = \"npm\",\n            relaxed = true,\n            version_check = function(version)\n                -- Parses output such as \"8.1.2\" into major, minor, patch components\n                local _, _, major = version:find \"(%d+)%.(%d+)%.(%d+)\"\n                -- Based off of general observations of feature parity.\n                -- In npm v7, peerDependencies are now automatically installed.\n                if tonumber(major) < 7 then\n                    return \"npm version must be >= 7\"\n                end\n            end,\n        },\n        check_thunk {\n            cmd = \"node\",\n            args = { \"--version\" },\n            name = \"node\",\n            relaxed = true,\n            version_check = function(version)\n                -- Parses output such as \"v16.3.1\" into major, minor, patch\n                local _, _, major = version:find \"v(%d+)%.(%d+)%.(%d+)\"\n                if tonumber(major) < 14 then\n                    return \"Node version must be >= 14\"\n                end\n            end,\n        },\n        check_thunk { cmd = \"javac\", args = { \"-version\" }, name = \"javac\", relaxed = true },\n        check_thunk { cmd = \"java\", args = { \"-version\" }, name = \"java\", use_stderr = true, relaxed = true },\n        check_thunk { cmd = \"julia\", args = { \"--version\" }, name = \"julia\", relaxed = true },\n        function()\n            local python = platform.is.win and \"python\" or \"python3\"\n            check { cmd = python, args = { \"--version\" }, name = \"python\", relaxed = true }\n            check { cmd = python, args = { \"-m\", \"pip\", \"--version\" }, name = \"pip\", relaxed = true }\n            check {\n                cmd = python,\n                args = { \"-c\", \"import venv\" },\n                name = \"python venv\",\n                relaxed = true,\n                advice = {\n                    [[On Debian/Ubuntu systems, you need to install the python3-venv package using the following command:\n\n    apt-get install python3-venv]],\n                },\n            }\n        end,\n        function()\n            a.scheduler()\n            if vim.env.JAVA_HOME then\n                check {\n                    cmd = (\"%s/bin/java\"):format(vim.env.JAVA_HOME),\n                    args = { \"-version\" },\n                    name = \"JAVA_HOME\",\n                    use_stderr = true,\n                    relaxed = true,\n                }\n            end\n        end,\n    }\nend\n\n---@async\nlocal function check_mason()\n    providers.github\n        .get_latest_release(\"mason-org/mason.nvim\")\n        :on_success(\n            ---@param latest_release GitHubRelease\n            function(latest_release)\n                a.scheduler()\n                if latest_release.tag_name ~= version.VERSION then\n                    report_warn((\"mason.nvim version %s\"):format(version.VERSION), {\n                        (\"The latest version of mason.nvim is: %s\"):format(latest_release.tag_name),\n                    })\n                else\n                    report_ok((\"mason.nvim version %s\"):format(version.VERSION))\n                end\n            end\n        )\n        :on_failure(function()\n            a.scheduler()\n            report_ok((\"mason.nvim version %s\"):format(version.VERSION))\n        end)\n\n    report_ok((\"PATH: %s\"):format(settings.current.PATH))\n    report_ok((\"Providers: \\n  %s\"):format(_.join(\"\\n  \", settings.current.providers)))\nend\n\nfunction M.check()\n    report_start \"mason.nvim\"\n\n    a.run_blocking(function()\n        check_mason()\n        check_neovim()\n        check_registries()\n        check_core_utils()\n        check_languages()\n        a.wait(vim.schedule)\n    end)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason/init.lua",
    "content": "local InstallLocation = require \"mason-core.installer.InstallLocation\"\nlocal Registry = require \"mason-registry\"\nlocal settings = require \"mason.settings\"\n\nlocal M = {}\n\nlocal function setup_autocmds()\n    vim.api.nvim_create_autocmd(\"VimLeavePre\", {\n        callback = function()\n            require(\"mason-core.terminator\").terminate(5000)\n        end,\n        once = true,\n    })\nend\n\nM.has_setup = false\n\n---@param config MasonSettings?\nfunction M.setup(config)\n    if config then\n        settings.set(config)\n    end\n\n    local global_location = InstallLocation.global()\n    global_location:set_env { PATH = settings.current.PATH }\n    for _, registry in ipairs(settings.current.registries) do\n        Registry.sources:append(registry)\n    end\n\n    require \"mason.api.command\"\n    setup_autocmds()\n    M.has_setup = true\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason/providers/client/gh.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal fetch = require \"mason-core.fetch\"\nlocal spawn = require \"mason-core.spawn\"\n\nlocal stringify_params = _.compose(_.join \"&\", _.map(_.join \"=\"), _.sort_by(_.head), _.to_pairs)\n\n---@param path string\n---@param opts { params: table<string, any>? }?\n---@return Result # JSON decoded response.\nlocal function gh_api_call(path, opts)\n    if opts and opts.params then\n        local params = stringify_params(opts.params)\n        path = (\"%s?%s\"):format(path, params)\n    end\n    return spawn\n        .gh({ \"api\", path, env = { CLICOLOR_FORCE = 0 } })\n        :map(_.prop \"stdout\")\n        :or_else(function()\n            return fetch((\"https://api.github.com/%s\"):format(path), {\n                headers = {\n                    Accept = \"application/vnd.github.v3+json; q=1.0, application/json; q=0.8\",\n                },\n            })\n        end)\n        :map_catching(vim.json.decode)\nend\n\n---@type GitHubProvider\nreturn {\n    get_latest_release = function(repo)\n        local path = (\"repos/%s/releases/latest\"):format(repo)\n        return gh_api_call(path)\n    end,\n    get_all_release_versions = function(repo)\n        local path = (\"repos/%s/releases\"):format(repo)\n        return gh_api_call(path):map(_.map(_.prop \"tag_name\"))\n    end,\n    get_all_tags = function(repo)\n        local path = (\"repos/%s/git/matching-refs/tags\"):format(repo)\n        return gh_api_call(path):map(_.map(_.compose(_.gsub(\"^refs/tags/\", \"\"), _.prop \"ref\")))\n    end,\n}\n"
  },
  {
    "path": "lua/mason/providers/client/golang.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal spawn = require \"mason-core.spawn\"\n\n---@type GolangProvider\nreturn {\n    get_all_versions = function(pkg)\n        return spawn\n            .go({\n                \"list\",\n                \"-json\",\n                \"-m\",\n                \"-versions\",\n                pkg,\n            })\n            :map(_.prop \"stdout\")\n            :map_catching(vim.json.decode)\n            :map(_.prop \"Versions\")\n            :map(_.reverse)\n    end,\n}\n"
  },
  {
    "path": "lua/mason/providers/client/init.lua",
    "content": "---@type Provider\nreturn {\n    github = require \"mason.providers.client.gh\",\n    npm = require \"mason.providers.client.npm\",\n    pypi = require \"mason.providers.client.pypi\",\n    rubygems = require \"mason.providers.client.rubygems\",\n    golang = require \"mason.providers.client.golang\",\n    openvsx = require \"mason.providers.client.openvsx\",\n}\n"
  },
  {
    "path": "lua/mason/providers/client/npm.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal spawn = require \"mason-core.spawn\"\n\n---@type NpmProvider\nreturn {\n    get_latest_version = function(pkg)\n        return spawn\n            .npm({ \"view\", \"--json\", pkg .. \"@latest\" })\n            :map(_.prop \"stdout\")\n            :map_catching(vim.json.decode)\n            :map(_.pick { \"name\", \"version\" })\n    end,\n    get_all_versions = function(pkg)\n        return spawn\n            .npm({ \"view\", \"--json\", pkg, \"versions\" })\n            :map(_.prop \"stdout\")\n            :map_catching(vim.json.decode)\n            :map(_.reverse)\n    end,\n}\n"
  },
  {
    "path": "lua/mason/providers/client/openvsx.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal fetch = require \"mason-core.fetch\"\nlocal semver = require \"mason-core.semver\"\n\n---@param path string\nlocal function api_url(path)\n    return (\"https://open-vsx.org/api/%s\"):format(path)\nend\n\n---@param version string\nlocal function maybe_semver_sort(version)\n    return semver.parse(version):get_or_else(version)\nend\n\n---@type OpenVSXProvider\nreturn {\n    get_latest_version = function(namespace, extension)\n        return fetch(api_url(\"%s/%s\"):format(namespace, extension)):map_catching(vim.json.decode):map(_.prop \"version\")\n    end,\n    get_all_versions = function(namespace, extension)\n        return fetch(api_url(\"%s/%s/versions\"):format(namespace, extension))\n            :map_catching(vim.json.decode)\n            :map(_.compose(_.keys, _.prop \"versions\"))\n            :map(_.compose(_.reverse, _.sort_by(maybe_semver_sort)))\n    end,\n}\n"
  },
  {
    "path": "lua/mason/providers/client/pypi.lua",
    "content": "local Optional = require \"mason-core.optional\"\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal fetch = require \"mason-core.fetch\"\nlocal fs = require \"mason-core.fs\"\nlocal platform = require \"mason-core.platform\"\nlocal spawn = require \"mason-core.spawn\"\n\n---@param args SpawnArgs\nlocal function python(args)\n    a.scheduler()\n    local py_exec = platform.is.win and \"python\" or \"python3\"\n    -- run in tmpdir in case pip inadvertently produces some output\n    args.cwd = vim.fn.tempname()\n    fs.async.mkdir(args.cwd)\n    return spawn[py_exec](args)\nend\n\n---@async\n---@param pkg string\nlocal function get_all_versions(pkg)\n    -- https://stackoverflow.com/a/26664162\n    return python({\n            \"-m\",\n            \"pip\",\n            \"install\",\n            \"--disable-pip-version-check\",\n            \"--use-deprecated=legacy-resolver\", -- for pip >= 20.3\n            (\"%s==\"):format(pkg), -- invalid version specifier to trigger the wanted error message\n        })\n        :recover(_.prop \"stderr\")\n        :map(_.compose(_.split \", \", _.head, _.match \"%(from versions: (.+)%)\"))\n        :map(_.reverse)\nend\n\n---@param pkg string\nlocal function synthesize_pkg(pkg)\n    ---@param version Optional\n    return function(version)\n        return version\n            :map(function(v)\n                return { name = pkg, version = v }\n            end)\n            :ok_or \"Unable to find latest version.\"\n    end\nend\n\n---@type PyPiProvider\nreturn {\n    get_latest_version = function(pkg)\n        return get_all_versions(pkg):map(_.compose(Optional.of_nilable, _.last)):and_then(synthesize_pkg(pkg))\n    end,\n    get_all_versions = get_all_versions,\n    get_supported_python_versions = function(pkg, version)\n        return fetch((\"https://pypi.org/pypi/%s/%s/json\"):format(pkg, version))\n            :map_catching(vim.json.decode)\n            :map(_.path { \"info\", \"requires_python\" })\n            :and_then(function(requires_python)\n                if type(requires_python) ~= \"string\" or requires_python == \"\" then\n                    return Result.failure \"Package does not specify supported Python versions.\"\n                else\n                    return Result.success(requires_python)\n                end\n            end)\n    end,\n}\n"
  },
  {
    "path": "lua/mason/providers/client/rubygems.lua",
    "content": "local Optional = require \"mason-core.optional\"\nlocal _ = require \"mason-core.functional\"\nlocal spawn = require \"mason-core.spawn\"\n\n---@param gem string\n---@param output string \"$ gem list\" output\nlocal parse_gem_versions = _.curryN(function(gem, output)\n    local lines = _.split(\"\\n\", output)\n    return Optional.of_nilable(_.find_first(_.starts_with(gem), lines))\n        :map(_.compose(_.head, _.match \"%((.+)%)$\"))\n        :map(_.split \", \")\n        :ok_or \"Failed to parse gem list output.\"\nend, 2)\n\n---@async\n---@param gem string\nlocal function get_all_versions(gem)\n    return spawn.gem({ \"list\", gem, \"--remote\", \"--all\" }):map(_.prop \"stdout\"):and_then(parse_gem_versions(gem))\nend\n\n---@type RubyGemsProvider\nreturn {\n    get_latest_version = function(gem)\n        return get_all_versions(gem):map(_.head)\n    end,\n    get_all_versions = function(gem)\n        return get_all_versions(gem)\n    end,\n}\n"
  },
  {
    "path": "lua/mason/providers/registry-api/init.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal api = require \"mason-registry.api\"\n\n---@type Provider\nreturn {\n    github = {\n        get_latest_release = function(repo)\n            return api.github.releases.latest { repo = repo }\n        end,\n        get_all_release_versions = function(repo)\n            return api.github.releases.all { repo = repo }\n        end,\n        get_latest_tag = function(repo)\n            return api.github.tags.latest { repo = repo }\n        end,\n        get_all_tags = function(repo)\n            return api.github.tags.all { repo = repo }\n        end,\n    },\n    npm = {\n        get_latest_version = function(pkg)\n            return api.npm.versions.latest { package = pkg }\n        end,\n        get_all_versions = function(pkg)\n            return api.npm.versions.all { package = pkg }\n        end,\n    },\n    pypi = {\n        get_latest_version = function(pkg)\n            return api.pypi.versions.latest { package = pkg }\n        end,\n        get_all_versions = function(pkg)\n            return api.pypi.versions.all { package = pkg }\n        end,\n        get_supported_python_versions = function(pkg, version)\n            return api.pypi.versions\n                .get({ package = pkg, version = version })\n                :map(_.prop \"requires_python\")\n                :and_then(function(requires_python)\n                    if type(requires_python) ~= \"string\" or requires_python == \"\" then\n                        return Result.failure \"Package does not specify supported Python versions.\"\n                    else\n                        return Result.success(requires_python)\n                    end\n                end)\n        end,\n    },\n    rubygems = {\n        get_latest_version = function(gem)\n            return api.rubygems.versions.latest { gem = gem }\n        end,\n        get_all_versions = function(gem)\n            return api.rubygems.versions.all { gem = gem }\n        end,\n    },\n    packagist = {\n        get_latest_version = function(pkg)\n            return api.packagist.versions.latest { pkg = pkg }\n        end,\n        get_all_versions = function(pkg)\n            return api.packagist.versions.all { pkg = pkg }\n        end,\n    },\n    crates = {\n        get_latest_version = function(crate)\n            return api.crate.versions.latest { crate = crate }\n        end,\n        get_all_versions = function(crate)\n            return api.crate.versions.all { crate = crate }\n        end,\n    },\n    golang = {\n        get_all_versions = function(pkg)\n            return api.golang.versions.all { pkg = api.encode_uri_component(pkg) }\n        end,\n    },\n    openvsx = {\n        get_latest_version = function(namespace, extension)\n            return api.openvsx.versions.latest { namespace = namespace, extension = extension }\n        end,\n        get_all_versions = function(namespace, extension)\n            return api.openvsx.versions.all { namespace = namespace, extension = extension }\n        end,\n    },\n}\n"
  },
  {
    "path": "lua/mason/settings.lua",
    "content": "local path = require \"mason-core.path\"\n\nlocal M = {}\n\n---@class MasonSettings\nlocal DEFAULT_SETTINGS = {\n    ---@since 1.0.0\n    -- The directory in which to install packages.\n    install_root_dir = path.concat { vim.fn.stdpath \"data\", \"mason\" },\n\n    ---@since 1.0.0\n    -- Where Mason should put its bin location in your PATH. Can be one of:\n    -- - \"prepend\" (default, Mason's bin location is put first in PATH)\n    -- - \"append\" (Mason's bin location is put at the end of PATH)\n    -- - \"skip\" (doesn't modify PATH)\n    ---@type '\"prepend\"' | '\"append\"' | '\"skip\"'\n    PATH = \"prepend\",\n\n    ---@since 1.0.0\n    -- Controls to which degree logs are written to the log file. It's useful to set this to vim.log.levels.DEBUG when\n    -- debugging issues with package installations.\n    log_level = vim.log.levels.INFO,\n\n    ---@since 1.0.0\n    -- Limit for the maximum amount of packages to be installed at the same time. Once this limit is reached, any further\n    -- packages that are requested to be installed will be put in a queue.\n    max_concurrent_installers = 4,\n\n    ---@since 1.0.0\n    -- [Advanced setting]\n    -- The registries to source packages from. Accepts multiple entries. Should a package with the same name exist in\n    -- multiple registries, the registry listed first will be used.\n    registries = {\n        \"github:mason-org/mason-registry\",\n    },\n\n    ---@since 1.0.0\n    -- The provider implementations to use for resolving supplementary package metadata (e.g., all available versions).\n    -- Accepts multiple entries, where later entries will be used as fallback should prior providers fail.\n    -- Builtin providers are:\n    --   - mason.providers.registry-api  - uses the https://api.mason-registry.dev API\n    --   - mason.providers.client        - uses only client-side tooling to resolve metadata\n    providers = {\n        \"mason.providers.registry-api\",\n        \"mason.providers.client\",\n    },\n\n    github = {\n        ---@since 1.0.0\n        -- The template URL to use when downloading assets from GitHub.\n        -- The placeholders are the following (in order):\n        -- 1. The repository (e.g. \"rust-lang/rust-analyzer\")\n        -- 2. The release version (e.g. \"v0.3.0\")\n        -- 3. The asset name (e.g. \"rust-analyzer-v0.3.0-x86_64-unknown-linux-gnu.tar.gz\")\n        download_url_template = \"https://github.com/%s/releases/download/%s/%s\",\n    },\n\n    pip = {\n        ---@since 1.0.0\n        -- Whether to upgrade pip to the latest version in the virtual environment before installing packages.\n        upgrade_pip = false,\n\n        ---@since 1.0.0\n        -- These args will be added to `pip install` calls. Note that setting extra args might impact intended behavior\n        -- and is not recommended.\n        --\n        -- Example: { \"--proxy\", \"https://proxyserver\" }\n        install_args = {},\n    },\n\n    ui = {\n        ---@since 1.0.0\n        -- Whether to automatically check for new versions when opening the :Mason window.\n        check_outdated_packages_on_open = true,\n\n        ---@since 1.0.0\n        -- The border to use for the UI window. Accepts same border values as |nvim_open_win()|.\n        -- Defaults to `:h 'winborder'` if nil.\n        border = nil,\n\n        ---@since 1.11.0\n        -- The backdrop opacity. 0 is fully opaque, 100 is fully transparent.\n        backdrop = 60,\n\n        ---@since 1.0.0\n        -- Width of the window. Accepts:\n        -- - Integer greater than 1 for fixed width.\n        -- - Float in the range of 0-1 for a percentage of screen width.\n        width = 0.8,\n\n        ---@since 1.0.0\n        -- Height of the window. Accepts:\n        -- - Integer greater than 1 for fixed height.\n        -- - Float in the range of 0-1 for a percentage of screen height.\n        height = 0.9,\n\n        icons = {\n            ---@since 1.0.0\n            -- The list icon to use for installed packages.\n            package_installed = \"◍\",\n            ---@since 1.0.0\n            -- The list icon to use for packages that are installing, or queued for installation.\n            package_pending = \"◍\",\n            ---@since 1.0.0\n            -- The list icon to use for packages that are not installed.\n            package_uninstalled = \"◍\",\n        },\n\n        keymaps = {\n            ---@since 1.0.0\n            -- Keymap to expand a package\n            toggle_package_expand = \"<CR>\",\n            ---@since 1.0.0\n            -- Keymap to install the package under the current cursor position\n            install_package = \"i\",\n            ---@since 1.0.0\n            -- Keymap to reinstall/update the package under the current cursor position\n            update_package = \"u\",\n            ---@since 1.0.0\n            -- Keymap to check for new version for the package under the current cursor position\n            check_package_version = \"c\",\n            ---@since 1.0.0\n            -- Keymap to update all installed packages\n            update_all_packages = \"U\",\n            ---@since 1.0.0\n            -- Keymap to check which installed packages are outdated\n            check_outdated_packages = \"C\",\n            ---@since 1.0.0\n            -- Keymap to uninstall a package\n            uninstall_package = \"X\",\n            ---@since 1.0.0\n            -- Keymap to cancel a package installation\n            cancel_installation = \"<C-c>\",\n            ---@since 1.0.0\n            -- Keymap to apply language filter\n            apply_language_filter = \"<C-f>\",\n            ---@since 1.1.0\n            -- Keymap to toggle viewing package installation log\n            toggle_package_install_log = \"<CR>\",\n            ---@since 1.8.0\n            -- Keymap to toggle the help view\n            toggle_help = \"g?\",\n        },\n    },\n}\n\nM._DEFAULT_SETTINGS = DEFAULT_SETTINGS\nM.current = M._DEFAULT_SETTINGS\n\n---@param opts MasonSettings\nfunction M.set(opts)\n    M.current = vim.tbl_deep_extend(\"force\", vim.deepcopy(M.current), opts)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason/ui/colors.lua",
    "content": "local hl_groups = {\n    MasonBackdrop = { bg = \"#000000\", default = true },\n    MasonNormal = { link = \"NormalFloat\", default = true },\n    MasonHeader = { bold = true, fg = \"#222222\", bg = \"#DCA561\", default = true },\n    MasonHeaderSecondary = { bold = true, fg = \"#222222\", bg = \"#56B6C2\", default = true },\n\n    MasonHighlight = { fg = \"#56B6C2\", default = true },\n    MasonHighlightBlock = { bg = \"#56B6C2\", fg = \"#222222\", default = true },\n    MasonHighlightBlockBold = { bg = \"#56B6C2\", fg = \"#222222\", bold = true, default = true },\n\n    MasonHighlightSecondary = { fg = \"#DCA561\", default = true },\n    MasonHighlightBlockSecondary = { bg = \"#DCA561\", fg = \"#222222\", default = true },\n    MasonHighlightBlockBoldSecondary = { bg = \"#DCA561\", fg = \"#222222\", bold = true, default = true },\n\n    MasonLink = { link = \"MasonHighlight\", default = true },\n\n    MasonMuted = { fg = \"#888888\", default = true },\n    MasonMutedBlock = { bg = \"#888888\", fg = \"#222222\", default = true },\n    MasonMutedBlockBold = { bg = \"#888888\", fg = \"#222222\", bold = true, default = true },\n\n    MasonError = { link = \"ErrorMsg\", default = true },\n    MasonWarning = { link = \"WarningMsg\", default = true },\n\n    MasonHeading = { bold = true, default = true },\n}\n\nfor name, hl in pairs(hl_groups) do\n    vim.api.nvim_set_hl(0, name, hl)\nend\n"
  },
  {
    "path": "lua/mason/ui/components/header.lua",
    "content": "local Ui = require \"mason-core.ui\"\nlocal _ = require \"mason-core.functional\"\nlocal p = require \"mason.ui.palette\"\nlocal settings = require \"mason.settings\"\nlocal version = require \"mason.version\"\n\n---@param state InstallerUiState\nreturn function(state)\n    local uninstalled_registries = _.filter(_.prop_eq(\"is_installed\", false), state.info.registries)\n\n    return Ui.Node {\n        Ui.CascadingStyleNode({ \"CENTERED\" }, {\n            Ui.HlTextNode {\n                Ui.When(state.view.is_showing_help, {\n                    p.header_secondary(\" \" .. state.header.title_prefix .. \" mason.nvim \"),\n                    p.header_secondary(version.VERSION .. \" \"),\n                    p.none((\" \"):rep(#state.header.title_prefix + 1)),\n                }, {\n                    p.header \" mason.nvim \",\n                    p.header(version.VERSION .. \" \"),\n                    state.view.is_searching and p.Comment \" (search mode, press <Esc> to clear)\" or p.none \"\",\n                }),\n                Ui.When(state.view.is_showing_help, {\n                    p.none \"        press \",\n                    p.highlight_secondary(settings.current.ui.keymaps.toggle_help),\n                    p.none \" for package list\",\n                }, {\n                    p.none \"press \",\n                    p.highlight(settings.current.ui.keymaps.toggle_help),\n                    p.none \" for help\",\n                }),\n                { p.Comment \"https://github.com/mason-org/mason.nvim\" },\n            },\n        }),\n        Ui.When(not state.info.registry_update.in_progress and #uninstalled_registries > 0, function()\n            return Ui.CascadingStyleNode({ \"INDENT\" }, {\n                Ui.EmptyLine(),\n                Ui.HlTextNode {\n                    {\n                        p.warning \"Uninstalled registries\",\n                    },\n                    {\n                        p.Comment \"Packages from the following registries are unavailable. Press \",\n                        p.highlight(settings.current.ui.keymaps.check_outdated_packages),\n                        p.Comment \" to install.\",\n                    },\n                    unpack(_.map(function(registry)\n                        return { p.none(\" - \" .. registry.name) }\n                    end, uninstalled_registries)),\n                },\n                Ui.EmptyLine(),\n            })\n        end),\n        Ui.When(\n            not state.info.registry_update.in_progress and state.info.registry_update.error,\n            Ui.CascadingStyleNode({ \"INDENT\" }, {\n                Ui.HlTextNode {\n                    {\n                        p.error \"Registry installation failed with the following error:\",\n                    },\n                    {\n                        p.none \"  \",\n                        p.Comment(state.info.registry_update.error),\n                    },\n                },\n                Ui.EmptyLine(),\n            })\n        ),\n    }\nend\n"
  },
  {
    "path": "lua/mason/ui/components/help/dap.lua",
    "content": "local Ui = require \"mason-core.ui\"\nlocal p = require \"mason.ui.palette\"\n\n---@param state InstallerUiState\nreturn function(state)\n    return Ui.HlTextNode {\n        {\n            p.Bold \"What is DAP?\",\n        },\n        {\n            p.none \"The \",\n            p.highlight_secondary \"D\",\n            p.none \"ebugger \",\n            p.highlight_secondary \"A\",\n            p.none \"dapter \",\n            p.highlight_secondary \"P\",\n            p.none \"rotocol defines the abstract protocol used\",\n        },\n        {\n            p.none \"between a development tool (e.g. IDE or editor) and a debugger.\",\n        },\n        {\n            p.none \"This provides editors with a standardized interface for enabling debugging\",\n        },\n        {\n            p.none \"capabilities - such as pausing execution, stepping through statements,\",\n        },\n        { p.none \"and inspecting variables.\" },\n        {},\n        { p.none \"For more information, see:\" },\n        { p.none \" - https://microsoft.github.io/debug-adapter-protocol/\" },\n    }\nend\n"
  },
  {
    "path": "lua/mason/ui/components/help/formatter.lua",
    "content": "local Ui = require \"mason-core.ui\"\nlocal p = require \"mason.ui.palette\"\n\n---@param state InstallerUiState\nreturn function(state)\n    return Ui.HlTextNode {\n        {\n            p.Bold \"What is a formatter?\",\n        },\n        { p.none \"A code formatter is a tool that reformats code to fit a certain\" },\n        { p.none \"formatting convention. This usually entails things like adjusting\" },\n        { p.none \"indentation, breaking long lines into smaller lines, adding or\" },\n        { p.none \"removing whitespaces. Formatting rules are often included as a\" },\n        { p.none \"separate configuration file within the project.\" },\n    }\nend\n"
  },
  {
    "path": "lua/mason/ui/components/help/init.lua",
    "content": "local Ui = require \"mason-core.ui\"\nlocal _ = require \"mason-core.functional\"\nlocal log = require \"mason-core.log\"\nlocal p = require \"mason.ui.palette\"\nlocal settings = require \"mason.settings\"\n\nlocal DAPHelp = require \"mason.ui.components.help.dap\"\nlocal FormatterHelp = require \"mason.ui.components.help.formatter\"\nlocal LSPHelp = require \"mason.ui.components.help.lsp\"\nlocal LinterHelp = require \"mason.ui.components.help.linter\"\n\n---@param state InstallerUiState\nlocal function Ship(state)\n    local ship_indent = { (\" \"):rep(state.view.ship_indentation), \"\" }\n    -- stylua: ignore start\n    local ship = {\n        { ship_indent,             p.muted \"/^v^\\\\\", p.none \"         |    |    |\" },\n        { ship_indent,                          p.none \"             )_)  )_)  )_)     \", p.muted \"/^v^\\\\\" },\n        { ship_indent, p.muted \"   \", p.muted \"/^v^\\\\\", p.none \"    )___))___))___)\\\\     \", p.highlight_secondary(state.view.ship_exclamation) },\n        { ship_indent,                          p.none \"           )____)____)_____)\\\\\\\\\" },\n        { ship_indent,                          p.none \"         _____|____|____|____\\\\\\\\\\\\__\" },\n        { ship_indent,  p.muted \"         \",            p.none \"\\\\                   /\" },\n    }\n    -- stylua: ignore end\n    local water = {\n        { p.highlight \"  ^^^^^ ^^^^^^^^  ^^^^^ ^^^^^  ^^^^^ ^^^^ <><  \" },\n        { p.highlight \"    ^^^^  ^^  ^^^    ^ ^^^    ^^^ <>< ^^^^     \" },\n        { p.highlight \"     ><> ^^^     ^^    ><> ^^     ^^    ^      \" },\n    }\n    if state.view.ship_indentation < 0 then\n        for _, shipline in ipairs(ship) do\n            local removed_chars = 0\n            for _, span in ipairs(shipline) do\n                local span_length = #span[1]\n                local chars_to_remove = (math.abs(state.view.ship_indentation) - removed_chars)\n                span[1] = string.sub(span[1], chars_to_remove + 1)\n                removed_chars = removed_chars + (span_length - #span[1])\n            end\n        end\n    end\n    return Ui.Node {\n        Ui.HlTextNode(ship),\n        Ui.HlTextNode(water),\n    }\nend\n\n---@param state InstallerUiState\nlocal function GenericHelp(state)\n    local keymap_tuples = {\n        { \"Toggle help\", settings.current.ui.keymaps.toggle_help },\n        { \"Toggle package info\", settings.current.ui.keymaps.toggle_package_expand },\n        { \"Toggle package installation log\", settings.current.ui.keymaps.toggle_package_install_log },\n        { \"Apply language filter\", settings.current.ui.keymaps.apply_language_filter },\n        { \"Install package\", settings.current.ui.keymaps.install_package },\n        { \"Uninstall package\", settings.current.ui.keymaps.uninstall_package },\n        { \"Update package\", settings.current.ui.keymaps.update_package },\n        { \"Update all outdated packages\", settings.current.ui.keymaps.update_all_packages },\n        { \"Check for new package version\", settings.current.ui.keymaps.check_package_version },\n        { \"Check for new versions (all packages)\", settings.current.ui.keymaps.check_outdated_packages },\n        { \"Cancel installation of package\", settings.current.ui.keymaps.cancel_installation },\n        { \"Close window\", \"q\" },\n        { \"Close window\", \"<Esc>\" },\n    }\n\n    local is_current_settings_expanded = state.view.is_current_settings_expanded\n\n    return Ui.Node {\n        Ui.HlTextNode {\n            { p.muted \"Mason log: \", p.none(log.outfile) },\n        },\n        Ui.EmptyLine(),\n        Ui.HlTextNode {\n            {\n                p.Bold \"Registries\",\n            },\n            {\n                p.muted \"Packages are sourced from the following registries:\",\n            },\n            unpack(_.map(function(registry)\n                return { p.none(\" - \" .. registry.name) }\n            end, state.info.registries)),\n        },\n        Ui.EmptyLine(),\n        Ui.Table {\n            {\n                p.Bold \"Keyboard shortcuts\",\n            },\n            unpack(_.map(function(keymap_tuple)\n                return { p.muted(keymap_tuple[1]), p.highlight(keymap_tuple[2]) }\n            end, keymap_tuples)),\n        },\n        Ui.EmptyLine(),\n        Ui.HlTextNode {\n            { p.Bold \"Problems installing packages\" },\n            {\n                p.muted \"Make sure you meet the minimum requirements to install packages. For debugging, refer to:\",\n            },\n        },\n        Ui.CascadingStyleNode({ \"INDENT\" }, {\n            Ui.HlTextNode {\n                {\n                    p.highlight \":help mason-debugging\",\n                },\n                {\n                    p.highlight \":checkhealth mason\",\n                },\n            },\n        }),\n        Ui.EmptyLine(),\n        Ui.HlTextNode {\n            { p.Bold \"Problems with package functionality\" },\n            { p.muted \"Please refer to each package's own homepage for further assistance.\" },\n        },\n        Ui.EmptyLine(),\n        Ui.HlTextNode {\n            { p.Bold \"How do I use installed packages?\" },\n            { p.muted \"Mason only makes packages available for use. It does not automatically integrate\" },\n            { p.muted \"these into Neovim. You have multiple different options for using any given\" },\n            { p.muted \"package, and you are free to pick and choose as you see fit.\" },\n            {\n                p.muted \"See \",\n                p.highlight \":help mason-how-to-use-packages\",\n                p.muted \" for a recommendation.\",\n            },\n        },\n        Ui.EmptyLine(),\n        Ui.HlTextNode {\n            { p.Bold \"Missing a package?\" },\n            { p.muted \"Please consider contributing to mason.nvim:\" },\n        },\n        Ui.CascadingStyleNode({ \"INDENT\" }, {\n            Ui.HlTextNode {\n                {\n                    p.none \"- \",\n                    p.highlight \"https://github.com/mason-org/mason.nvim/blob/main/CONTRIBUTING.md\",\n                },\n                {\n                    p.none \"- \",\n                    p.highlight \"https://github.com/mason-org/mason.nvim/blob/main/doc/reference.md\",\n                },\n            },\n        }),\n        Ui.EmptyLine(),\n        Ui.HlTextNode {\n            {\n                p.Bold((\"%s Current settings\"):format(is_current_settings_expanded and \"↓\" or \"→\")),\n                p.highlight \" :help mason-settings\",\n            },\n        },\n        Ui.Keybind(settings.current.ui.keymaps.toggle_package_expand, \"TOGGLE_EXPAND_CURRENT_SETTINGS\", nil),\n        Ui.When(is_current_settings_expanded, function()\n            local settings_split_by_newline = vim.split(vim.inspect(settings.current), \"\\n\")\n            local current_settings = _.map(function(line)\n                return { p.muted(line) }\n            end, settings_split_by_newline)\n            return Ui.HlTextNode(current_settings)\n        end),\n    }\nend\n\n---@param state InstallerUiState\nreturn function(state)\n    ---@type INode\n    local heading = Ui.Node {}\n    if state.view.current == \"LSP\" then\n        heading = Ui.Node {\n            LSPHelp(state),\n            Ui.EmptyLine(),\n        }\n    elseif state.view.current == \"DAP\" then\n        heading = Ui.Node {\n            DAPHelp(state),\n            Ui.EmptyLine(),\n        }\n    elseif state.view.current == \"Linter\" then\n        heading = Ui.Node {\n            LinterHelp(state),\n            Ui.EmptyLine(),\n        }\n    elseif state.view.current == \"Formatter\" then\n        heading = Ui.Node {\n            FormatterHelp(state),\n            Ui.EmptyLine(),\n        }\n    end\n\n    return Ui.CascadingStyleNode({ \"INDENT\" }, {\n        Ui.HlTextNode(state.view.has_changed and p.none \"\" or p.Comment \"(change view by pressing its number)\"),\n        heading,\n        GenericHelp(state),\n        Ui.EmptyLine(),\n        Ship(state),\n        Ui.EmptyLine(),\n    })\nend\n"
  },
  {
    "path": "lua/mason/ui/components/help/linter.lua",
    "content": "local Ui = require \"mason-core.ui\"\nlocal p = require \"mason.ui.palette\"\n\n---@param state InstallerUiState\nreturn function(state)\n    return Ui.HlTextNode {\n        {\n            p.Bold \"What is a linter?\",\n        },\n        { p.none \"A linter is a static code analysis tool used to provide diagnostics around\" },\n        { p.none \"programming errors, bugs, stylistic errors and suspicious constructs.\" },\n        { p.none \"Linters can be executed as a standalone program in a terminal, where it\" },\n        { p.none \"usually expects one or more input files to lint. There are also Neovim plugins\" },\n        { p.none \"that integrate these diagnostics inside the editor.\" },\n    }\nend\n"
  },
  {
    "path": "lua/mason/ui/components/help/lsp.lua",
    "content": "local Ui = require \"mason-core.ui\"\nlocal p = require \"mason.ui.palette\"\n\n---@param state InstallerUiState\nreturn function(state)\n    return Ui.HlTextNode {\n        {\n            p.Bold \"What is LSP?\",\n        },\n        {\n            p.none \"The \",\n            p.highlight_secondary \"L\",\n            p.none \"anguage \",\n            p.highlight_secondary \"S\",\n            p.none \"erver \",\n            p.highlight_secondary \"P\",\n            p.none \"rotocol defines the protocol used between an\",\n        },\n        {\n            p.none \"editor or IDE and a language server that provides language features\",\n        },\n        {\n            p.none \"like auto complete, go to definition, find all references etc.\",\n        },\n        {},\n        {\n            p.none \"The term \",\n            p.highlight_secondary \"LSP\",\n            p.none \" is often used to reference a server implementation of\",\n        },\n        { p.none \"the LSP protocol.\" },\n        {},\n        { p.none \"For more information, see:\" },\n        { p.none \" - https://microsoft.github.io/language-server-protocol/\" },\n        { p.none \" - \", p.highlight \":help lsp\" },\n    }\nend\n"
  },
  {
    "path": "lua/mason/ui/components/json-schema.lua",
    "content": "-- Here be dragons\nlocal Ui = require \"mason-core.ui\"\nlocal _ = require \"mason-core.functional\"\nlocal settings = require \"mason.settings\"\n\nlocal property_type_highlights = {\n    [\"string\"] = \"String\",\n    [\"string[]\"] = \"String\",\n    [\"boolean\"] = \"Boolean\",\n    [\"number\"] = \"Number\",\n    [\"number[]\"] = \"Number\",\n    [\"integer\"] = \"Number\",\n    [\"integer[]\"] = \"Number\",\n}\n\nlocal function resolve_type(property_schema)\n    if _.is_list(property_schema.type) then\n        return table.concat(property_schema.type, \" | \")\n    elseif property_schema.type == \"array\" then\n        if property_schema.items then\n            return (\"%s[]\"):format(property_schema.items.type)\n        else\n            return property_schema.type\n        end\n    end\n\n    return property_schema.type or \"N/A\"\nend\n\nlocal function Indent(indentation, children)\n    -- create a list table with as many \"INDENT\" entries as the numeric indentation variable\n    local indent = {}\n    for _ = 1, indentation do\n        table.insert(indent, \"INDENT\")\n    end\n    return Ui.CascadingStyleNode(indent, children)\nend\n\n---@param pkg Package\n---@param schema_id string\n---@param state UiPackageState\n---@param schema table\n---@param key string?\n---@param level number?\n---@param key_width number? The width the key should occupate in the UI to produce an even column.\n---@param compound_key string?\nlocal function JsonSchema(pkg, schema_id, state, schema, key, level, key_width, compound_key)\n    level = level or 0\n    compound_key = (\"%s%s\"):format(compound_key or \"\", key or \"\")\n    local toggle_expand_keybind = Ui.Keybind(\n        settings.current.ui.keymaps.toggle_package_expand,\n        \"TOGGLE_JSON_SCHEMA_KEY\",\n        { package = pkg, schema_id = schema_id, key = compound_key }\n    )\n    local node_is_expanded = state.expanded_json_schema_keys[schema_id][compound_key]\n    local key_prefix = node_is_expanded and \"↓ \" or \"→ \"\n\n    if (schema.type == \"object\" or schema.type == nil) and schema.properties then\n        local nodes = {}\n        if key then\n            -- This node belongs to some parent object - render a heading for it.\n            -- It'll act as the anchor for its children.\n            local heading = Ui.HlTextNode {\n                key_prefix .. key,\n                node_is_expanded and \"Bold\" or \"\",\n            }\n            nodes[#nodes + 1] = heading\n            nodes[#nodes + 1] = toggle_expand_keybind\n        end\n\n        -- All level 0 nodes are expanded by default - otherwise we'd not render anything at all\n        if level == 0 or node_is_expanded then\n            local max_property_length = 0\n            local sorted_properties = {}\n            for property in pairs(schema.properties) do\n                max_property_length = math.max(max_property_length, vim.api.nvim_strwidth(property))\n                sorted_properties[#sorted_properties + 1] = property\n            end\n            table.sort(sorted_properties)\n            for _, property in ipairs(sorted_properties) do\n                nodes[#nodes + 1] = Indent(level, {\n                    JsonSchema(\n                        pkg,\n                        schema_id,\n                        state,\n                        schema.properties[property],\n                        property,\n                        level + 1,\n                        max_property_length,\n                        compound_key\n                    ),\n                })\n            end\n        end\n        return Ui.Node(nodes)\n    elseif schema.oneOf then\n        local nodes = {}\n        for i, alternative_schema in ipairs(schema.oneOf) do\n            nodes[#nodes + 1] = JsonSchema(\n                pkg,\n                schema_id,\n                state,\n                alternative_schema,\n                (\"%s (alt. %d)\"):format(key, i),\n                level,\n                key_width,\n                compound_key\n            )\n        end\n        return Ui.Node(nodes)\n    elseif _.is_list(schema) then\n        return Ui.Node(_.map(function(sub_schema)\n            return JsonSchema(pkg, schema_id, state, sub_schema)\n        end, schema))\n    elseif level > 0 then -- Leaf nodes cannot occupy the root level.\n        -- Leaf node (aka any type that isn't an object).\n        local type = resolve_type(schema)\n        local heading\n        local label = (key_prefix .. key .. (\" \"):rep(key_width or 0)):sub(1, key_width + 5) -- + 5 to account for key_prefix plus some extra whitespace\n        if schema.default ~= nil then\n            heading = Ui.HlTextNode {\n                {\n                    {\n                        label,\n                        node_is_expanded and \"Bold\" or \"\",\n                    },\n                    {\n                        \" default: \",\n                        \"Comment\",\n                    },\n                    {\n                        vim.json.encode(schema.default),\n                        property_type_highlights[type] or \"MasonMuted\",\n                    },\n                },\n            }\n        else\n            heading = Ui.HlTextNode {\n                label,\n                node_is_expanded and \"Bold\" or \"\",\n            }\n        end\n\n        return Ui.Node {\n            heading,\n            toggle_expand_keybind,\n            Ui.When(node_is_expanded, function()\n                local description = _.map(function(line)\n                    return { { line, \"Comment\" } }\n                end, vim.split(schema.description or \"No description available.\", \"\\n\"))\n\n                local type_highlight = property_type_highlights[type] or \"MasonMuted\"\n\n                local table_rows = {\n                    { { \"type\", \"MasonMuted\" }, { type, type_highlight } },\n                }\n\n                if _.is_list(schema.enum) then\n                    for idx, enum in ipairs(schema.enum) do\n                        local enum_description = \"\"\n                        if schema.enumDescriptions and schema.enumDescriptions[idx] then\n                            enum_description = \"- \" .. schema.enumDescriptions[idx]\n                        end\n                        table_rows[#table_rows + 1] = {\n                            { idx == 1 and \"possible values\" or \"\", \"MasonMuted\" },\n                            { vim.json.encode(enum), type_highlight },\n                            { enum_description, \"Comment\" },\n                        }\n                    end\n                end\n\n                return Indent(level, {\n                    Ui.HlTextNode(description),\n                    Ui.Table(table_rows),\n                })\n            end),\n        }\n    else\n        return Ui.Node {}\n    end\nend\n\nreturn JsonSchema\n"
  },
  {
    "path": "lua/mason/ui/components/language-filter.lua",
    "content": "local Ui = require \"mason-core.ui\"\nlocal p = require \"mason.ui.palette\"\nlocal settings = require \"mason.settings\"\n\n---@param state InstallerUiState\nreturn function(state)\n    return Ui.CascadingStyleNode({ \"INDENT\" }, {\n        Ui.When(state.view.language_filter, function()\n            return Ui.Node {\n                Ui.EmptyLine(),\n                Ui.HlTextNode {\n                    {\n                        p.Bold \"Language Filter: \",\n                        p.highlight(state.view.language_filter),\n                        p.Comment \" press <Esc> to clear\",\n                    },\n                },\n            }\n        end),\n        Ui.When(not state.view.language_filter, function()\n            return Ui.Node {\n                Ui.EmptyLine(),\n                Ui.HlTextNode {\n                    {\n                        p.Bold \"Language Filter:\",\n                        p.Comment(\n                            (\" press %s to apply filter\"):format(settings.current.ui.keymaps.apply_language_filter)\n                        ),\n                    },\n                },\n            }\n        end),\n    })\nend\n"
  },
  {
    "path": "lua/mason/ui/components/main/init.lua",
    "content": "local Ui = require \"mason-core.ui\"\n\nlocal PackageList = require \"mason.ui.components.main.package_list\"\n\n---@param state InstallerUiState\nreturn function(state)\n    return Ui.Node {\n        Ui.EmptyLine(),\n        PackageList(state),\n    }\nend\n"
  },
  {
    "path": "lua/mason/ui/components/main/package_list.lua",
    "content": "local Ui = require \"mason-core.ui\"\nlocal _ = require \"mason-core.functional\"\nlocal p = require \"mason.ui.palette\"\nlocal settings = require \"mason.settings\"\n\nlocal JsonSchema = require \"mason.ui.components.json-schema\"\n\n---@param props { state: InstallerUiState, heading: INode, packages: Package[], list_item_renderer: (fun(package: Package, state: InstallerUiState): INode), hide_when_empty: boolean }\nlocal function PackageListContainer(props)\n    local items = {}\n    for i = 1, #props.packages do\n        local pkg = props.packages[i]\n        if props.state.packages.visible[pkg.name] then\n            items[#items + 1] = props.list_item_renderer(pkg, props.state)\n        end\n    end\n\n    if props.hide_when_empty and #items == 0 then\n        return Ui.Node {}\n    end\n\n    return Ui.Node {\n        props.heading,\n        Ui.VirtualTextNode { p.Comment((\"(%d)\"):format(#items)) },\n        Ui.CascadingStyleNode({ \"INDENT\" }, items),\n        Ui.When(\n            #items == 0,\n            Ui.CascadingStyleNode({ \"CENTERED\" }, {\n                Ui.HlTextNode(p.Comment \"No packages.\"),\n            })\n        ),\n        Ui.EmptyLine(),\n    }\nend\n\n---@param executables table<string, string>?\nlocal function ExecutablesTable(executables)\n    if not executables or _.size(executables) == 0 then\n        return Ui.Node {}\n    end\n    local rows = {}\n    for executable in pairs(executables) do\n        table.insert(rows, { p.none \"\", p.Bold(executable) })\n    end\n    rows[1][1] = p.muted \"executables\"\n    return rows\nend\n\n---@param state InstallerUiState\n---@param pkg Package\n---@param is_installed boolean\nlocal function ExpandedPackageInfo(state, pkg, is_installed)\n    local pkg_state = state.packages.states[pkg.name]\n    return Ui.CascadingStyleNode({ \"INDENT\" }, {\n        Ui.When(not is_installed and pkg.spec.deprecation, function()\n            return Ui.HlTextNode(p.warning((\"Deprecation message: %s\"):format(pkg.spec.deprecation.message)))\n        end),\n        Ui.HlTextNode(_.map(function(line)\n            return { p.Comment(line) }\n        end, _.split(\"\\n\", pkg.spec.description))),\n        Ui.EmptyLine(),\n        Ui.Table(_.concat(\n            _.filter(_.identity, {\n                is_installed and {\n                    p.muted \"installed version\",\n                    pkg_state.version and p.Bold(pkg_state.version) or p.muted \"-\",\n                } or {\n                    p.muted \"version\",\n                    p.Bold(pkg:get_latest_version()),\n                },\n                pkg_state.new_version and {\n                    p.muted \"latest version\",\n                    p.muted(pkg_state.new_version),\n                },\n                pkg_state.installed_purl and {\n                    p.muted \"installed purl\",\n                    p.highlight(pkg_state.installed_purl),\n                } or {\n                    p.muted \"purl\",\n                    p.highlight(pkg.spec.source.id),\n                },\n                {\n                    p.muted \"homepage\",\n                    pkg.spec.homepage and p.highlight(pkg.spec.homepage) or p.muted \"-\",\n                },\n                {\n                    p.muted \"languages\",\n                    #pkg.spec.languages > 0 and p.Bold(table.concat(pkg.spec.languages, \", \")) or p.muted \"-\",\n                },\n                {\n                    p.muted \"categories\",\n                    #pkg.spec.categories > 0 and p.Bold(table.concat(pkg.spec.categories, \", \")) or p.muted \"-\",\n                },\n            }),\n            Ui.When(is_installed, function()\n                return ExecutablesTable(pkg_state.linked_executables)\n            end)\n        )),\n        Ui.When(pkg_state.lsp_settings_schema ~= nil, function()\n            local has_expanded = pkg_state.expanded_json_schemas[\"lsp\"]\n            return Ui.Node {\n                Ui.EmptyLine(),\n                Ui.HlTextNode {\n                    {\n                        p.Bold((\"%s LSP server configuration schema\"):format(has_expanded and \"↓\" or \"→\")),\n                        p.Comment((\" (press enter to %s)\"):format(has_expanded and \"collapse\" or \"expand\")),\n                    },\n                },\n                Ui.Keybind(\n                    settings.current.ui.keymaps.toggle_package_expand,\n                    \"TOGGLE_JSON_SCHEMA\",\n                    { package = pkg, schema_id = \"lsp\" }\n                ),\n                Ui.When(has_expanded, function()\n                    return Ui.CascadingStyleNode({ \"INDENT\" }, {\n                        Ui.HlTextNode(\n                            p.muted \"This is a read-only overview of the settings this server accepts. Note that some settings might not apply to neovim.\"\n                        ),\n                        Ui.EmptyLine(),\n                        JsonSchema(pkg, \"lsp\", pkg_state, pkg_state.lsp_settings_schema),\n                    })\n                end),\n            }\n        end),\n        Ui.EmptyLine(),\n    })\nend\n\nlocal get_package_search_keywords = _.compose(_.join \", \", _.map(_.to_lower), _.path { \"spec\", \"languages\" })\n\n---@param state InstallerUiState\n---@param pkg Package\n---@param opts { keybinds: KeybindHandlerNode[], icon: string[], is_installed: boolean, sticky: StickyCursorNode? }\nlocal function PackageComponent(state, pkg, opts)\n    local pkg_state = state.packages.states[pkg.name]\n    local is_expanded = state.packages.expanded == pkg.name\n    local label = (is_expanded or pkg_state.has_transitioned) and p.Bold(\" \" .. pkg.name) or p.none(\" \" .. pkg.name)\n\n    local package_line = {\n        opts.icon,\n        label,\n    }\n\n    local pkg_aliases = pkg:get_aliases()\n    if #pkg_aliases > 0 then\n        package_line[#package_line + 1] = p.Comment(\" \" .. table.concat(pkg:get_aliases(), \", \"))\n    end\n    if state.view.is_searching then\n        package_line[#package_line + 1] = p.Comment((\" (keywords: %s)\"):format(get_package_search_keywords(pkg)))\n    end\n    if not opts.is_installed and pkg.spec.deprecation ~= nil then\n        package_line[#package_line + 1] = p.warning \" deprecated\"\n    end\n\n    return Ui.Node {\n        Ui.HlTextNode { package_line },\n        opts.sticky or Ui.Node {},\n        Ui.When(opts.is_installed and pkg.spec.deprecation ~= nil, function()\n            return Ui.DiagnosticsNode {\n                message = (\"deprecated: %s\"):format(pkg.spec.deprecation.message),\n                severity = vim.diagnostic.severity.WARN,\n                source = (\"Deprecated since version %s\"):format(pkg.spec.deprecation.since),\n            }\n        end),\n        Ui.Keybind(settings.current.ui.keymaps.check_package_version, \"CHECK_NEW_PACKAGE_VERSION\", pkg),\n        Ui.When(pkg_state.new_version ~= nil, function()\n            return Ui.DiagnosticsNode {\n                message = (\"new version available: %s -> %s\"):format(pkg_state.version or \"-\", pkg_state.new_version),\n                severity = vim.diagnostic.severity.INFO,\n            }\n        end),\n        Ui.Node(opts.keybinds),\n        Ui.When(is_expanded, function()\n            return ExpandedPackageInfo(state, pkg, opts.is_installed)\n        end),\n    }\nend\n\nlocal get_outdated_packages_preview = _.if_else(\n    _.compose(_.lte(4), _.size),\n    _.compose(_.join \", \", _.map(_.prop \"name\")),\n    _.compose(\n        _.join \", \",\n        _.converge(_.concat, {\n            _.compose(_.map(_.prop \"name\"), _.take(3)),\n            function(pkgs)\n                return { (\"and %d more…\"):format(#pkgs - 3) }\n            end,\n        })\n    )\n)\n\n---@param state InstallerUiState\nlocal function Installed(state)\n    return Ui.Node {\n        Ui.Keybind(settings.current.ui.keymaps.check_outdated_packages, \"UPDATE_REGISTRY\", nil, true),\n        PackageListContainer {\n            state = state,\n            heading = Ui.Node {\n                Ui.HlTextNode(p.heading \"Installed\"),\n                Ui.When(state.info.registry_update.in_progress, function()\n                    local styling = state.info.registry_update.percentage_complete == 1 and p.highlight_block\n                        or p.muted_block\n                    local is_all_registries_installed = _.all(_.prop \"is_installed\", state.info.registries)\n                    local registry_count = #state.info.registries\n                    local text\n                    if registry_count > 1 then\n                        text = p.Comment(\n                            is_all_registries_installed and (\"updating %d registries \"):format(registry_count)\n                                or (\"installing %d registries \"):format(registry_count)\n                        )\n                    else\n                        text = p.Comment(is_all_registries_installed and \"updating registry \" or \"installing registry \")\n                    end\n                    return Ui.VirtualTextNode {\n                        text,\n                        styling(\n                            (\"%-4s\"):format(math.floor(state.info.registry_update.percentage_complete * 100) .. \"%\")\n                        ),\n                        styling((\" \"):rep(state.info.registry_update.percentage_complete * 15)),\n                    }\n                end),\n                Ui.When(\n                    not state.info.registry_update.in_progress and #state.packages.outdated_packages > 0,\n                    function()\n                        return Ui.VirtualTextNode {\n                            p.muted \"Press \",\n                            p.highlight(settings.current.ui.keymaps.update_all_packages),\n                            p.muted \" to update \",\n                            p.highlight(tostring(#state.packages.outdated_packages)),\n                            p.muted(#state.packages.outdated_packages > 1 and \" packages \" or \" package \"),\n                            p.Comment((\"(%s)\"):format(get_outdated_packages_preview(state.packages.outdated_packages))),\n                        }\n                    end\n                ),\n            },\n            packages = state.packages.installed,\n            ---@param pkg Package\n            list_item_renderer = function(pkg)\n                return PackageComponent(state, pkg, {\n                    is_installed = true,\n                    icon = p.highlight(settings.current.ui.icons.package_installed),\n                    keybinds = {\n                        Ui.Keybind(settings.current.ui.keymaps.update_package, \"INSTALL_PACKAGE\", pkg),\n                        Ui.Keybind(settings.current.ui.keymaps.uninstall_package, \"UNINSTALL_PACKAGE\", pkg),\n                        Ui.Keybind(settings.current.ui.keymaps.toggle_package_expand, \"TOGGLE_EXPAND_PACKAGE\", pkg),\n                    },\n                    sticky = Ui.StickyCursor { id = (\"%s-installed\"):format(pkg.name) },\n                })\n            end,\n        },\n    }\nend\n\n---@param pkg Package\n---@param state InstallerUiState\nlocal function InstallingPackageComponent(pkg, state)\n    ---@type UiPackageState\n    local pkg_state = state.packages.states[pkg.name]\n    local current_state = pkg_state.is_terminated and p.Comment \" (cancelling)\" or p.none \"\"\n    local tail = pkg_state.short_tailed_output\n            and (\"▶ # [%d/%d] %s\"):format(\n                #pkg_state.tailed_output,\n                #pkg_state.tailed_output,\n                pkg_state.short_tailed_output\n            )\n        or \"\"\n    return Ui.Node {\n        Ui.HlTextNode {\n            {\n                pkg_state.has_failed and p.error(settings.current.ui.icons.package_uninstalled)\n                    or p.highlight(settings.current.ui.icons.package_pending),\n                p.none(\" \" .. pkg.name),\n                current_state,\n                pkg_state.latest_spawn and p.Comment((\" $ %s\"):format(pkg_state.latest_spawn)) or p.none \"\",\n            },\n        },\n        Ui.StickyCursor { id = (\"%s-installing\"):format(pkg.name) },\n        Ui.Keybind(settings.current.ui.keymaps.cancel_installation, \"TERMINATE_PACKAGE_HANDLE\", pkg),\n        Ui.Keybind(settings.current.ui.keymaps.install_package, \"INSTALL_PACKAGE\", pkg),\n        Ui.CascadingStyleNode({ \"INDENT\" }, {\n            Ui.HlTextNode(pkg_state.is_log_expanded and p.Bold \"▼ Displaying full log\" or p.muted(tail)),\n            Ui.Keybind(settings.current.ui.keymaps.toggle_package_install_log, \"TOGGLE_INSTALL_LOG\", pkg),\n            Ui.StickyCursor { id = (\"%s-toggle-install-log\"):format(pkg.name) },\n        }),\n        Ui.When(pkg_state.is_log_expanded, function()\n            return Ui.CascadingStyleNode({ \"INDENT\", \"INDENT\" }, {\n                Ui.HlTextNode(_.map(function(line)\n                    return { p.muted(line) }\n                end, pkg_state.tailed_output)),\n            })\n        end),\n    }\nend\n\n---@param state InstallerUiState\nlocal function Installing(state)\n    local packages = state.packages.installing\n    return PackageListContainer {\n        state = state,\n        heading = Ui.Node {\n            Ui.HlTextNode(p.heading \"Installing\"),\n            Ui.StickyCursor { id = \"installing-section\" },\n            Ui.Keybind(settings.current.ui.keymaps.cancel_installation, \"TERMINATE_PACKAGE_HANDLES\", packages),\n        },\n        hide_when_empty = true,\n        packages = packages,\n        ---@param pkg Package\n        list_item_renderer = InstallingPackageComponent,\n    }\nend\n\n---@param state InstallerUiState\nlocal function Queued(state)\n    local packages = state.packages.queued\n    return PackageListContainer {\n        state = state,\n        heading = Ui.Node {\n            Ui.HlTextNode(p.heading \"Queued\"),\n            Ui.StickyCursor { id = \"queued-section\" },\n            Ui.Keybind(settings.current.ui.keymaps.cancel_installation, \"TERMINATE_PACKAGE_HANDLES\", packages),\n        },\n        packages = packages,\n        hide_when_empty = true,\n        ---@param pkg Package\n        list_item_renderer = function(pkg)\n            return Ui.Node {\n                Ui.HlTextNode {\n                    { p.highlight(settings.current.ui.icons.package_pending), p.none(\" \" .. pkg.name) },\n                },\n                Ui.StickyCursor { id = (\"%s-installing\"):format(pkg.spec.name) },\n                Ui.Keybind(settings.current.ui.keymaps.cancel_installation, \"TERMINATE_PACKAGE_HANDLE\", pkg),\n            }\n        end,\n    }\nend\n\n---@param state InstallerUiState\nlocal function Failed(state)\n    local packages = state.packages.failed\n    if #packages == 0 then\n        return Ui.Node {}\n    end\n    return PackageListContainer {\n        state = state,\n        heading = Ui.HlTextNode(p.heading \"Failed\"),\n        packages = packages,\n        list_item_renderer = InstallingPackageComponent,\n    }\nend\n\n---@param state InstallerUiState\nlocal function Uninstalled(state)\n    return PackageListContainer {\n        state = state,\n        heading = Ui.HlTextNode(p.heading \"Available\"),\n        packages = state.packages.uninstalled,\n        ---@param pkg Package\n        list_item_renderer = function(pkg)\n            return PackageComponent(state, pkg, {\n                icon = p.muted(settings.current.ui.icons.package_uninstalled),\n                keybinds = {\n                    Ui.Keybind(settings.current.ui.keymaps.install_package, \"INSTALL_PACKAGE\", pkg),\n                    Ui.Keybind(settings.current.ui.keymaps.toggle_package_expand, \"TOGGLE_EXPAND_PACKAGE\", pkg),\n                },\n                sticky = Ui.StickyCursor { id = (\"%s-uninstalled\"):format(pkg.name) },\n            })\n        end,\n    }\nend\n\n---@param state InstallerUiState\nreturn function(state)\n    return Ui.CascadingStyleNode({ \"INDENT\" }, {\n        Failed(state),\n        Installing(state),\n        Queued(state),\n        Installed(state),\n        Uninstalled(state),\n    })\nend\n"
  },
  {
    "path": "lua/mason/ui/components/tabs.lua",
    "content": "local Package = require \"mason-core.package\"\nlocal Ui = require \"mason-core.ui\"\nlocal p = require \"mason.ui.palette\"\n\n---@param text string\n---@param index integer\n---@param is_active boolean\n---@param use_secondary_highlight boolean\nlocal function create_tab_span(text, index, is_active, use_secondary_highlight)\n    local highlight_block = use_secondary_highlight and p.highlight_block_bold_secondary or p.highlight_block_bold\n\n    if is_active then\n        return {\n            highlight_block \" \",\n            highlight_block(\"(\" .. index .. \")\"),\n            highlight_block(\" \" .. text .. \" \"),\n            p.none \" \",\n        }\n    else\n        return {\n            p.muted_block \" \",\n            p.muted_block(\"(\" .. index .. \")\"),\n            p.muted_block(\" \" .. text .. \" \"),\n            p.none \" \",\n        }\n    end\nend\n\n---@param state InstallerUiState\nreturn function(state)\n    local tabs = {}\n    for i, text in ipairs { \"All\", Package.Cat.LSP, Package.Cat.DAP, Package.Cat.Linter, Package.Cat.Formatter } do\n        vim.list_extend(tabs, create_tab_span(text, i, state.view.current == text, state.view.is_showing_help))\n    end\n    return Ui.CascadingStyleNode({ \"INDENT\" }, {\n        Ui.HlTextNode { tabs },\n        Ui.StickyCursor { id = \"tabs\" },\n    })\nend\n"
  },
  {
    "path": "lua/mason/ui/init.lua",
    "content": "local M = {}\n\nfunction M.close()\n    local api = require \"mason.ui.instance\"\n    api.close()\nend\n\nfunction M.open()\n    local api = require \"mason.ui.instance\"\n    api.window.open()\nend\n\n---@param view string\nfunction M.set_view(view)\n    local api = require \"mason.ui.instance\"\n    api.set_view(view)\nend\n\n---@param tag any\nfunction M.set_sticky_cursor(tag)\n    local api = require \"mason.ui.instance\"\n    api.set_sticky_cursor(tag)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason/ui/instance.lua",
    "content": "-- !!!\n-- in dire need of rework, proceed with caution\n-- !!!\nlocal Package = require \"mason-core.package\"\nlocal Ui = require \"mason-core.ui\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal display = require \"mason-core.ui.display\"\nlocal notify = require \"mason-core.notify\"\nlocal registry = require \"mason-registry\"\nlocal settings = require \"mason.settings\"\n\nlocal Header = require \"mason.ui.components.header\"\nlocal Help = require \"mason.ui.components.help\"\nlocal LanguageFilter = require \"mason.ui.components.language-filter\"\nlocal Main = require \"mason.ui.components.main\"\nlocal Tabs = require \"mason.ui.components.tabs\"\n\nrequire \"mason.ui.colors\"\n\n---@param state InstallerUiState\nlocal function GlobalKeybinds(state)\n    return Ui.Node {\n        Ui.Keybind(settings.current.ui.keymaps.toggle_help, \"TOGGLE_HELP\", nil, true),\n        Ui.Keybind(\"q\", \"CLOSE_WINDOW\", nil, true),\n        Ui.When(not state.view.language_filter, Ui.Keybind(\"<Esc>\", \"CLOSE_WINDOW\", nil, true)),\n        Ui.When(state.view.language_filter, Ui.Keybind(\"<Esc>\", \"CLEAR_LANGUAGE_FILTER\", nil, true)),\n        Ui.When(state.view.is_searching, Ui.Keybind(\"<Esc>\", \"CLEAR_SEARCH_MODE\", nil, true)),\n        Ui.Keybind(settings.current.ui.keymaps.apply_language_filter, \"LANGUAGE_FILTER\", nil, true),\n        Ui.Keybind(settings.current.ui.keymaps.update_all_packages, \"UPDATE_ALL_PACKAGES\", nil, true),\n\n        Ui.Keybind(\"1\", \"SET_VIEW\", \"All\", true),\n        Ui.Keybind(\"2\", \"SET_VIEW\", \"LSP\", true),\n        Ui.Keybind(\"3\", \"SET_VIEW\", \"DAP\", true),\n        Ui.Keybind(\"4\", \"SET_VIEW\", \"Linter\", true),\n        Ui.Keybind(\"5\", \"SET_VIEW\", \"Formatter\", true),\n    }\nend\n\n---@class UiPackageState\n---@field expanded_json_schema_keys table<string, table<string, boolean>>\n---@field expanded_json_schemas table<string, boolean>\n---@field has_expanded_before boolean\n---@field has_transitioned boolean\n---@field is_terminated boolean\n---@field is_log_expanded boolean\n---@field has_failed boolean\n---@field latest_spawn string?\n---@field linked_executables table<string, string>?\n---@field installed_purl string?\n---@field lsp_settings_schema table?\n---@field new_version string?\n---@field short_tailed_output string?\n---@field tailed_output string[]\n---@field version string?\n\n---@class InstallerUiState\nlocal INITIAL_STATE = {\n    info = {\n        ---@type string | nil\n        used_disk_space = nil,\n        ---@type { name: string, is_installed: boolean }[]\n        registries = {},\n        registry_update = {\n            ---@type string?\n            error = nil,\n            in_progress = false,\n            percentage_complete = 0,\n        },\n    },\n    view = {\n        is_searching = false,\n        is_showing_help = false,\n        is_current_settings_expanded = false,\n        language_filter = nil,\n        current = \"All\",\n        has_changed = false,\n        ship_indentation = 0,\n        ship_exclamation = \"\",\n    },\n    header = {\n        title_prefix = \"\", -- for animation\n    },\n    packages = {\n        ---@type Package[]\n        outdated_packages = {},\n        ---@type Package[]\n        all = {},\n        ---@type table<string, boolean>\n        visible = {},\n        ---@type string|nil\n        expanded = nil,\n        ---@type table<string, UiPackageState>\n        states = {},\n        ---@type Package[]\n        installed = {},\n        ---@type Package[]\n        installing = {},\n        ---@type Package[]\n        failed = {},\n        ---@type Package[]\n        queued = {},\n        ---@type Package[]\n        uninstalled = {},\n    },\n}\n\n---@generic T\n---@param list T[]\n---@param item T\n---@return T\nlocal function remove(list, item)\n    for i, v in ipairs(list) do\n        if v == item then\n            table.remove(list, i)\n            return list\n        end\n    end\n    return list\nend\n\nlocal window = display.new_view_only_win(\"mason.nvim\", \"mason\")\n\nwindow.view(\n    ---@param state InstallerUiState\n    function(state)\n        return Ui.Node {\n            GlobalKeybinds(state),\n            Header(state),\n            Tabs(state),\n            Ui.When(state.view.is_showing_help, function()\n                return Help(state)\n            end),\n            Ui.When(not state.view.is_showing_help, function()\n                return Ui.Node {\n                    LanguageFilter(state),\n                    Main(state),\n                }\n            end),\n        }\n    end\n)\n\nlocal mutate_state, get_state = window.state(INITIAL_STATE)\n\nwindow.events:on(\"search:enter\", function()\n    mutate_state(function(state)\n        state.view.is_searching = true\n    end)\n    vim.schedule(function()\n        vim.cmd \"redraw\"\n    end)\nend)\n\nwindow.events:on(\"search:leave\", function(search)\n    if search == \"\" and vim.fn.getreg \"/\" == \"\" then\n        mutate_state(function(state)\n            state.view.is_searching = false\n        end)\n    end\nend)\n\n---@param pkg Package\n---@param group string\n---@param tail boolean? Whether to insert at the end.\nlocal function mutate_package_grouping(pkg, group, tail)\n    mutate_state(function(state)\n        remove(state.packages.installing, pkg)\n        remove(state.packages.queued, pkg)\n        remove(state.packages.uninstalled, pkg)\n        remove(state.packages.installed, pkg)\n        remove(state.packages.failed, pkg)\n        if tail then\n            table.insert(state.packages[group], pkg)\n        else\n            table.insert(state.packages[group], 1, pkg)\n        end\n        state.packages.states[pkg.name].has_transitioned = true\n    end)\nend\n\n---@param mutate_fn fun(state: InstallerUiState)\nlocal function mutate_package_visibility(mutate_fn)\n    mutate_state(function(state)\n        mutate_fn(state)\n        local view_predicate = {\n            [\"All\"] = _.T,\n            [\"LSP\"] = _.prop_satisfies(_.any(_.equals(Package.Cat.LSP)), \"categories\"),\n            [\"DAP\"] = _.prop_satisfies(_.any(_.equals(Package.Cat.DAP)), \"categories\"),\n            [\"Linter\"] = _.prop_satisfies(_.any(_.equals(Package.Cat.Linter)), \"categories\"),\n            [\"Formatter\"] = _.prop_satisfies(_.any(_.equals(Package.Cat.Formatter)), \"categories\"),\n        }\n        local language_predicate = _.if_else(\n            _.always(state.view.language_filter),\n            _.prop_satisfies(_.any(_.equals(state.view.language_filter)), \"languages\"),\n            _.T\n        )\n        for __, pkg in ipairs(state.packages.all) do\n            state.packages.visible[pkg.name] =\n                _.all_pass({ view_predicate[state.view.current], language_predicate }, pkg.spec)\n        end\n    end)\nend\n\n---@return UiPackageState\nlocal function create_initial_package_state()\n    return {\n        expanded_json_schema_keys = {},\n        expanded_json_schemas = {},\n        has_expanded_before = false,\n        has_transitioned = false,\n        is_terminated = false,\n        is_log_expanded = false,\n        has_failed = false,\n        latest_spawn = nil,\n        linked_executables = nil,\n        installed_purl = nil,\n        lsp_settings_schema = nil,\n        new_version = nil,\n        short_tailed_output = nil,\n        tailed_output = {},\n        version = nil,\n    }\nend\n\n---@param handle InstallHandle\nlocal function setup_handle(handle)\n    local function handle_state_change()\n        if handle.state == \"QUEUED\" then\n            mutate_package_grouping(handle.package, \"queued\", true)\n        elseif handle.state == \"ACTIVE\" then\n            mutate_package_grouping(handle.package, \"installing\", true)\n        elseif handle.state == \"CLOSED\" then\n            mutate_state(function(state)\n                state.packages.states[handle.package.name].is_terminated = false\n            end)\n        end\n    end\n\n    local function handle_spawnhandle_change()\n        mutate_state(function(state)\n            state.packages.states[handle.package.name].latest_spawn =\n                handle:peek_spawn_handle():map(tostring):map(_.gsub(\"\\n\", \"\\\\n \")):or_else(nil)\n        end)\n    end\n\n    ---@param chunk string\n    local function handle_output(chunk)\n        mutate_state(function(state)\n            local pkg_state = state.packages.states[handle.package.name]\n            local lines = vim.split(chunk, \"\\n\")\n            for i = 1, #lines do\n                local line = lines[i]\n                if i == 1 and pkg_state.tailed_output[#pkg_state.tailed_output] then\n                    pkg_state.tailed_output[#pkg_state.tailed_output] = pkg_state.tailed_output[#pkg_state.tailed_output]\n                        .. line\n                else\n                    pkg_state.tailed_output[#pkg_state.tailed_output + 1] = line\n                end\n                if not line:match \"^%s*$\" then\n                    pkg_state.short_tailed_output = line:gsub(\"^%s+\", \"\")\n                end\n            end\n        end)\n    end\n\n    local function handle_terminate()\n        mutate_state(function(state)\n            state.packages.states[handle.package.name].is_terminated = handle.is_terminated\n            if handle:is_queued() then\n                -- This is really already handled by the \"install:failed\" handler, but for UX reasons we handle\n                -- terminated, queued, handlers here. The reason for this is that a queued handler, which is\n                -- aborted, will not fail its installation until it acquires a semaphore permit, leading to a weird\n                -- UX that may be perceived as non-functional.\n                mutate_package_grouping(handle.package, handle.package:is_installed() and \"installed\" or \"uninstalled\")\n            end\n        end)\n    end\n\n    handle:on(\"terminate\", handle_terminate)\n    handle:on(\"state:change\", handle_state_change)\n    handle:on(\"spawn_handles:change\", handle_spawnhandle_change)\n    handle:on(\"stdout\", handle_output)\n    handle:on(\"stderr\", handle_output)\n\n    -- hydrate initial state\n    handle_state_change()\n    handle_spawnhandle_change()\n    mutate_state(function(state)\n        state.packages.states[handle.package.name] = create_initial_package_state()\n    end)\nend\n\n---@param pkg Package\nlocal function hydrate_detailed_package_state(pkg)\n    mutate_state(function(state)\n        -- initialize expanded keys table\n        state.packages.states[pkg.name].expanded_json_schema_keys[\"lsp\"] = state.packages.states[pkg.name].expanded_json_schema_keys[\"lsp\"]\n            or {}\n        state.packages.states[pkg.name].lsp_settings_schema = pkg:get_lsp_settings_schema():or_else(nil)\n        state.packages.states[pkg.name].version = pkg:get_installed_version()\n    end)\n\n    pkg:get_receipt():if_present(\n        ---@param receipt InstallReceipt\n        function(receipt)\n            mutate_state(function(state)\n                state.packages.states[pkg.name].linked_executables = receipt:get_links().bin\n                state.packages.states[pkg.name].installed_purl = receipt:get_installed_purl()\n            end)\n        end\n    )\nend\n\nlocal help_animation\ndo\n    local help_command = \":help\"\n    local help_command_len = #help_command\n    help_animation = Ui.animation {\n        function(tick)\n            mutate_state(function(state)\n                state.header.title_prefix = help_command:sub(help_command_len - tick, help_command_len)\n            end)\n        end,\n        range = { 0, help_command_len },\n        delay_ms = 80,\n    }\nend\n\nlocal ship_animation = Ui.animation {\n    function(tick)\n        mutate_state(function(state)\n            state.view.ship_indentation = tick\n            if tick > -5 then\n                state.view.ship_exclamation = \"https://github.com/sponsors/williamboman\"\n            elseif tick > -27 then\n                state.view.ship_exclamation = \"Sponsor mason.nvim development!\"\n            else\n                state.view.ship_exclamation = \"\"\n            end\n        end)\n    end,\n    range = { -35, 5 },\n    delay_ms = 250,\n}\n\nlocal function toggle_help()\n    mutate_state(function(state)\n        state.view.is_showing_help = not state.view.is_showing_help\n        if state.view.is_showing_help then\n            help_animation()\n            ship_animation()\n        end\n    end)\nend\n\nlocal function set_view(event)\n    local view = event.payload\n    mutate_package_visibility(function(state)\n        state.view.current = view\n        state.view.has_changed = true\n    end)\n    if window.is_open() then\n        local cursor_line = window.get_cursor()[1]\n        if cursor_line > (window.get_win_config().height * 0.75) then\n            window.set_sticky_cursor \"tabs\"\n        end\n    end\nend\n\nlocal function terminate_package_handle(event)\n    ---@type Package\n    local pkg = event.payload\n    pkg:get_install_handle():if_present(\n        ---@param handle InstallHandle\n        function(handle)\n            if not handle:is_closed() then\n                vim.schedule_wrap(notify)((\"Cancelling installation of %q.\"):format(pkg.name))\n                handle:terminate()\n            end\n        end\n    )\n\n    pkg:get_uninstall_handle():if_present(\n        ---@param handle InstallHandle\n        function(handle)\n            if not handle:is_closed() then\n                vim.schedule_wrap(notify)((\"Cancelling uninstallation of %q.\"):format(pkg.name))\n                handle:terminate()\n            end\n        end\n    )\nend\n\nlocal function terminate_all_package_handles(event)\n    ---@type Package[]\n    local pkgs = _.list_copy(event.payload) -- we copy because list is mutated while iterating it\n    for _, pkg in ipairs(pkgs) do\n        pkg:get_install_handle():if_present(\n            ---@param handle InstallHandle\n            function(handle)\n                if not handle:is_closed() then\n                    handle:terminate()\n                end\n            end\n        )\n    end\nend\n\nlocal function install_package(event)\n    ---@type AbstractPackage\n    local pkg = event.payload\n    if not pkg:is_installing() then\n        pkg:install()\n    end\n    mutate_state(function(state)\n        state.packages.outdated_packages = _.filter(_.complement(_.equals(pkg)), state.packages.outdated_packages)\n    end)\nend\n\nlocal function uninstall_package(event)\n    ---@type AbstractPackage\n    local pkg = event.payload\n    if not pkg:is_uninstalling() then\n        pkg:uninstall()\n    end\nend\n\nlocal function toggle_expand_package(event)\n    ---@type Package\n    local pkg = event.payload\n    mutate_state(function(state)\n        if state.packages.expanded == pkg.name then\n            state.packages.expanded = nil\n        else\n            if not state.packages.states[pkg.name].has_expanded_before then\n                hydrate_detailed_package_state(pkg)\n                state.packages.states[pkg.name].has_expanded_before = true\n            end\n            state.packages.expanded = pkg.name\n        end\n    end)\nend\n\n---@param pkg Package\nlocal function check_new_package_version(pkg)\n    local installed_version = pkg:get_installed_version()\n    mutate_state(function(state)\n        state.packages.states[pkg.name].version = installed_version\n    end)\n    local latest_version = pkg:get_latest_version()\n    if latest_version ~= installed_version and pkg:is_installable { version = latest_version } then\n        mutate_state(function(state)\n            state.packages.states[pkg.name].new_version = latest_version\n        end)\n        return true\n    else\n        mutate_state(function(state)\n            state.packages.states[pkg.name].new_version = nil\n        end)\n        return false\n    end\nend\n\nlocal function check_new_package_versions()\n    mutate_state(function(state)\n        local outdated_packages = {}\n        for _, pkg in ipairs(state.packages.installed) do\n            local current_version = pkg:get_installed_version()\n            local latest_version = pkg:get_latest_version()\n            if current_version ~= latest_version then\n                state.packages.states[pkg.name].version = current_version\n                state.packages.states[pkg.name].new_version = latest_version\n                table.insert(outdated_packages, pkg)\n            else\n                state.packages.states[pkg.name].new_version = nil\n            end\n        end\n        state.packages.outdated_packages = outdated_packages\n    end)\nend\n\nlocal function toggle_json_schema(event)\n    local package, schema_id = event.payload.package, event.payload.schema_id\n    mutate_state(function(state)\n        state.packages.states[package.name].expanded_json_schemas[schema_id] =\n            not state.packages.states[package.name].expanded_json_schemas[schema_id]\n    end)\nend\n\nlocal function toggle_json_schema_keys(event)\n    local package, schema_id, key = event.payload.package, event.payload.schema_id, event.payload.key\n    mutate_state(function(state)\n        state.packages.states[package.name].expanded_json_schema_keys[schema_id][key] =\n            not state.packages.states[package.name].expanded_json_schema_keys[schema_id][key]\n    end)\nend\n\nlocal function filter()\n    vim.ui.select(_.sort_by(_.identity, _.keys(Package.Lang)), {\n        prompt = \"Select language:\",\n        kind = \"mason.ui.language-filter\",\n    }, function(choice)\n        if not choice or choice == \"\" then\n            return\n        end\n        mutate_package_visibility(function(state)\n            state.view.language_filter = choice\n        end)\n    end)\nend\n\nlocal function clear_filter()\n    mutate_package_visibility(function(state)\n        state.view.language_filter = nil\n    end)\nend\n\nlocal function clear_search_mode()\n    mutate_state(function(state)\n        state.view.is_searching = false\n    end)\nend\n\nlocal function toggle_expand_current_settings()\n    mutate_state(function(state)\n        state.view.is_current_settings_expanded = not state.view.is_current_settings_expanded\n    end)\nend\n\nlocal function update_all_packages()\n    local state = get_state()\n    _.each(function(pkg)\n        pkg:install()\n    end, state.packages.outdated_packages)\n    mutate_state(function(state)\n        state.packages.outdated_packages = {}\n    end)\nend\n\nlocal function toggle_install_log(event)\n    ---@type Package\n    local pkg = event.payload\n    mutate_state(function(state)\n        state.packages.states[pkg.name].is_log_expanded = not state.packages.states[pkg.name].is_log_expanded\n    end)\nend\n\nlocal effects = {\n    [\"CHECK_NEW_PACKAGE_VERSION\"] = a.scope(_.compose(_.partial(pcall, check_new_package_version), _.prop \"payload\")),\n    [\"UPDATE_REGISTRY\"] = function()\n        registry.update()\n    end,\n    [\"CLEAR_LANGUAGE_FILTER\"] = clear_filter,\n    [\"CLEAR_SEARCH_MODE\"] = clear_search_mode,\n    [\"CLOSE_WINDOW\"] = window.close,\n    [\"INSTALL_PACKAGE\"] = install_package,\n    [\"LANGUAGE_FILTER\"] = filter,\n    [\"SET_VIEW\"] = set_view,\n    [\"TERMINATE_PACKAGE_HANDLE\"] = terminate_package_handle,\n    [\"TERMINATE_PACKAGE_HANDLES\"] = terminate_all_package_handles,\n    [\"TOGGLE_EXPAND_CURRENT_SETTINGS\"] = toggle_expand_current_settings,\n    [\"TOGGLE_EXPAND_PACKAGE\"] = toggle_expand_package,\n    [\"TOGGLE_HELP\"] = toggle_help,\n    [\"TOGGLE_INSTALL_LOG\"] = toggle_install_log,\n    [\"TOGGLE_JSON_SCHEMA\"] = toggle_json_schema,\n    [\"TOGGLE_JSON_SCHEMA_KEY\"] = toggle_json_schema_keys,\n    [\"UNINSTALL_PACKAGE\"] = uninstall_package,\n    [\"UPDATE_ALL_PACKAGES\"] = update_all_packages,\n}\n\nlocal registered_packages = {}\n\n---@param pkg Package\nlocal function setup_package(pkg)\n    if registered_packages[pkg] then\n        return\n    end\n\n    mutate_state(function(state)\n        for _, group in ipairs {\n            state.packages.installed,\n            state.packages.uninstalled,\n            state.packages.failed,\n            state.packages.outdated_packages,\n        } do\n            for i, existing_pkg in ipairs(group) do\n                if existing_pkg.name == pkg.name and pkg ~= existing_pkg then\n                    -- New package instance (i.e. from a new, updated, registry source).\n                    -- Release the old package instance.\n                    table.remove(group, i)\n                end\n            end\n        end\n    end)\n\n    -- hydrate initial state\n    mutate_state(function(state)\n        state.packages.states[pkg.name] = create_initial_package_state()\n        state.packages.visible[pkg.name] = true\n        table.insert(state.packages[pkg:is_installed() and \"installed\" or \"uninstalled\"], pkg)\n    end)\n\n    pkg:get_install_handle():if_present(setup_handle)\n    pkg:on(\"install:handle\", setup_handle)\n\n    pkg:on(\"install:success\", function()\n        vim.schedule(function()\n            notify((\"%s was successfully installed.\"):format(pkg.name))\n        end)\n        mutate_state(function(state)\n            state.packages.states[pkg.name] = create_initial_package_state()\n            if state.packages.expanded == pkg.name then\n                hydrate_detailed_package_state(pkg)\n            end\n        end)\n        mutate_package_grouping(pkg, \"installed\")\n    end)\n\n    pkg:on(\n        \"install:failed\",\n        ---@param handle InstallHandle\n        function(handle)\n            if handle.is_terminated then\n                -- If installation was explicitly terminated - restore to \"pristine\" state\n                mutate_state(function(state)\n                    state.packages.states[pkg.name] = create_initial_package_state()\n                end)\n                mutate_package_grouping(pkg, pkg:is_installed() and \"installed\" or \"uninstalled\")\n            else\n                vim.schedule(function()\n                    notify((\"%s failed to install.\"):format(pkg.name), vim.log.levels.ERROR)\n                end)\n                mutate_package_grouping(pkg, \"failed\")\n                mutate_state(function(state)\n                    state.packages.states[pkg.name].has_failed = true\n                end)\n            end\n        end\n    )\n\n    pkg:on(\"uninstall:success\", function()\n        if pkg:is_installing() then\n            -- We don't care about uninstallations that occur during installation because it's expected behaviour and\n            -- not constructive to surface to users.\n            return\n        end\n        vim.schedule(function()\n            notify((\"%s was successfully uninstalled.\"):format(pkg.name))\n        end)\n        mutate_state(function(state)\n            state.packages.states[pkg.name] = create_initial_package_state()\n            state.packages.outdated_packages = _.filter(_.complement(_.equals(pkg)), state.packages.outdated_packages)\n        end)\n        mutate_package_grouping(pkg, \"uninstalled\")\n    end)\n\n    registered_packages[pkg] = true\nend\n\nlocal function update_registry_info()\n    local registries = {}\n    for source in registry.sources:iterate { include_uninstalled = true, include_synthesized = false } do\n        table.insert(registries, {\n            name = source:get_display_name(),\n            is_installed = source:is_installed(),\n        })\n    end\n    mutate_state(function(state)\n        state.info.registries = registries\n    end)\nend\n\n---@param packages Package[]\nlocal function setup_packages(packages)\n    for _, pkg in ipairs(_.sort_by(_.prop \"name\", packages)) do\n        setup_package(pkg)\n    end\n    mutate_state(function(state)\n        state.packages.all = packages\n    end)\nend\n\nregistry:on(\"update:failed\", function(errors)\n    mutate_state(function(state)\n        state.info.registry_update.percentage_complete = 0\n        state.info.registry_update.in_progress = false\n        state.info.registry_update.error = table.concat(errors, \" - \")\n    end)\nend)\n\nregistry:on(\"update:success\", function()\n    setup_packages(registry.get_all_packages())\n    update_registry_info()\n    check_new_package_versions()\n\n    -- Wait with resetting the state in order to keep displaying the update message\n    vim.defer_fn(function()\n        mutate_state(function(state)\n            if state.info.registry_update.percentage_complete ~= 1 then\n                -- New update was started already, don't reset state\n                return\n            end\n            state.info.registry_update.in_progress = false\n            state.info.registry_update.percentage_complete = 0\n        end)\n    end, 1000)\nend)\n\nregistry:on(\"update:start\", function()\n    mutate_state(function(state)\n        state.packages.outdated_packages = {}\n        state.info.registry_update.error = nil\n        state.info.registry_update.in_progress = true\n        state.info.registry_update.percentage_complete = 0\n    end)\nend)\n\nregistry:on(\"update:progress\", function(finished, all)\n    mutate_state(function(state)\n        state.info.registry_update.percentage_complete = #finished / #all\n    end)\nend)\n\nupdate_registry_info()\nif registry.sources:is_all_installed() then\n    setup_packages(registry.get_all_packages())\nend\n\nif settings.current.ui.check_outdated_packages_on_open then\n    registry.update()\nelse\n    registry.refresh(function(success, updated_registries)\n        if success and #updated_registries == 0 then\n            setup_packages(registry.get_all_packages())\n            update_registry_info()\n        end\n    end)\nend\n\nlocal border = settings.current.ui.border\n\nif border == nil and vim.fn.exists \"&winborder\" == 0 then\n    border = \"none\"\nend\n\nwindow.init {\n    effects = effects,\n    border = border,\n    winhighlight = {\n        \"NormalFloat:MasonNormal\",\n    },\n}\n\nreturn {\n    window = window,\n    set_view = function(view)\n        set_view { payload = view }\n    end,\n    set_sticky_cursor = function(tag)\n        window.set_sticky_cursor(tag)\n    end,\n}\n"
  },
  {
    "path": "lua/mason/ui/palette.lua",
    "content": "local M = {}\n\nlocal function hl(highlight)\n    return function(text)\n        return { text, highlight }\n    end\nend\n\n-- aliases\nM.none = hl \"\"\nM.header = hl \"MasonHeader\"\nM.header_secondary = hl \"MasonHeaderSecondary\"\nM.muted = hl \"MasonMuted\"\nM.muted_block = hl \"MasonMutedBlock\"\nM.muted_block_bold = hl \"MasonMutedBlockBold\"\nM.highlight = hl \"MasonHighlight\"\nM.highlight_block = hl \"MasonHighlightBlock\"\nM.highlight_block_bold = hl \"MasonHighlightBlockBold\"\nM.highlight_block_secondary = hl \"MasonHighlightBlockSecondary\"\nM.highlight_block_bold_secondary = hl \"MasonHighlightBlockBoldSecondary\"\nM.highlight_secondary = hl \"MasonHighlightSecondary\"\nM.error = hl \"MasonError\"\nM.warning = hl \"MasonWarning\"\nM.heading = hl \"MasonHeading\"\n\nsetmetatable(M, {\n    __index = function(self, key)\n        self[key] = hl(key)\n        return self[key]\n    end,\n})\n\nreturn M\n"
  },
  {
    "path": "lua/mason/version.lua",
    "content": "local M = {}\n\nM.VERSION = \"v2.2.1\" -- x-release-please-version\nM.MAJOR_VERSION = 2 -- x-release-please-major\nM.MINOR_VERSION = 2 -- x-release-please-minor\nM.PATCH_VERSION = 1 -- x-release-please-patch\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/EventEmitter.lua",
    "content": "local log = require \"mason-core.log\"\n---@class EventEmitter\n---@field private __event_handlers table<any, table<fun(), fun()>>\n---@field private __event_handlers_once table<any, table<fun(), fun()>>\nlocal EventEmitter = {}\nEventEmitter.__index = EventEmitter\n\nfunction EventEmitter:new()\n    local instance = {}\n    setmetatable(instance, self)\n    instance.__event_handlers = {}\n    instance.__event_handlers_once = {}\n    return instance\nend\n\n---@generic T\n---@param obj T\n---@return T\nfunction EventEmitter.init(obj)\n    obj.__event_handlers = {}\n    obj.__event_handlers_once = {}\n    return obj\nend\n\n---@param event any\n---@param handler fun(...): any\nlocal function call_handler(event, handler, ...)\n    local ok, err = pcall(handler, ...)\n    if not ok then\n        log.fmt_warn(\"EventEmitter handler failed for event %s with error %s\", event, err)\n    end\nend\n\n---@param event any\nfunction EventEmitter:emit(event, ...)\n    if self.__event_handlers[event] then\n        for handler in pairs(self.__event_handlers[event]) do\n            call_handler(event, handler, ...)\n        end\n    end\n    if self.__event_handlers_once[event] then\n        for handler in pairs(self.__event_handlers_once[event]) do\n            call_handler(event, handler, ...)\n            self.__event_handlers_once[event][handler] = nil\n        end\n    end\n    return self\nend\n\n---@param event any\n---@param handler fun(payload: any)\nfunction EventEmitter:on(event, handler)\n    if not self.__event_handlers[event] then\n        self.__event_handlers[event] = {}\n    end\n    self.__event_handlers[event][handler] = handler\n    return self\nend\n\n---@param event any\n---@param handler fun(payload: any)\nfunction EventEmitter:once(event, handler)\n    if not self.__event_handlers_once[event] then\n        self.__event_handlers_once[event] = {}\n    end\n    self.__event_handlers_once[event][handler] = handler\n    return self\nend\n\n---@param event any\n---@param handler fun(payload: any)\nfunction EventEmitter:off(event, handler)\n    if self.__event_handlers[event] then\n        self.__event_handlers[event][handler] = nil\n    end\n    if self.__event_handlers_once[event] then\n        self.__event_handlers_once[event][handler] = nil\n    end\n    return self\nend\n\n---@private\nfunction EventEmitter:__clear_event_handlers()\n    self.__event_handlers = {}\n    self.__event_handlers_once = {}\nend\n\nreturn EventEmitter\n"
  },
  {
    "path": "lua/mason-core/async/control.lua",
    "content": "local a = require \"mason-core.async\"\n\n---@class Condvar\nlocal Condvar = {}\nCondvar.__index = Condvar\n\nfunction Condvar:new()\n    ---@type Condvar\n    local instance = {}\n    setmetatable(instance, self)\n    instance.handles = {}\n    return instance\nend\n\n---@async\nfunction Condvar:wait()\n    a.wait(function(resolve)\n        self.handles[#self.handles + 1] = resolve\n    end)\nend\n\nfunction Condvar:notify()\n    local handle = table.remove(self.handles)\n    pcall(handle)\nend\n\nfunction Condvar:notify_all()\n    while #self.handles > 0 do\n        self:notify()\n    end\n    self.handles = {}\nend\n\n---@class Permit\nlocal Permit = {}\nPermit.__index = Permit\n\nfunction Permit:new(semaphore)\n    ---@type Permit\n    local instance = {}\n    setmetatable(instance, self)\n    instance.semaphore = semaphore\n    return instance\nend\n\nfunction Permit:forget()\n    local semaphore = self.semaphore\n    semaphore.permits = semaphore.permits + 1\n\n    if semaphore.permits > 0 and #semaphore.handles > 0 then\n        semaphore.permits = semaphore.permits - 1\n        local release = table.remove(semaphore.handles, 1)\n        release()\n    end\nend\n\n---@class Semaphore\nlocal Semaphore = {}\nSemaphore.__index = Semaphore\n\n---@param permits integer\nfunction Semaphore:new(permits)\n    ---@type Semaphore\n    local instance = {}\n    setmetatable(instance, self)\n    instance.permits = permits\n    instance.handles = {}\n    return instance\nend\n\n---@async\nfunction Semaphore:acquire()\n    if self.permits > 0 then\n        self.permits = self.permits - 1\n    else\n        a.wait(function(resolve)\n            table.insert(self.handles, resolve)\n        end)\n    end\n\n    return Permit:new(self)\nend\n\n---@class OneShotChannel\n---@field has_sent boolean\n---@field value any\n---@field condvar Condvar\nlocal OneShotChannel = {}\nOneShotChannel.__index = OneShotChannel\n\nfunction OneShotChannel:new()\n    ---@type OneShotChannel\n    local instance = {}\n    setmetatable(instance, self)\n    instance.has_sent = false\n    instance.value = nil\n    instance.condvar = Condvar:new()\n    return instance\nend\n\nfunction OneShotChannel:is_closed()\n    return self.has_sent\nend\n\nfunction OneShotChannel:send(...)\n    assert(not self.has_sent, \"Oneshot channel can only send once.\")\n    self.has_sent = true\n    self.value = { ... }\n    self.condvar:notify_all()\n    self.condvar = nil\nend\n\nfunction OneShotChannel:receive()\n    if not self.has_sent then\n        self.condvar:wait()\n    end\n    return unpack(self.value)\nend\n\n---@class Channel\n---@field private condvar Condvar\n---@field private buffer any?\n---@field is_closed boolean\nlocal Channel = {}\nChannel.__index = Channel\n\nfunction Channel:new()\n    ---@type Channel\n    local instance = {}\n    setmetatable(instance, self)\n    instance.condvar = Condvar:new()\n    instance.buffer = nil\n    instance.is_closed = false\n    return instance\nend\n\nfunction Channel:close()\n    self.is_closed = true\nend\n\n---@async\nfunction Channel:send(value)\n    assert(not self.is_closed, \"Channel is closed.\")\n    while self.buffer ~= nil do\n        self.condvar:wait()\n    end\n    self.buffer = value\n    self.condvar:notify()\n    while self.buffer ~= nil do\n        self.condvar:wait()\n    end\nend\n\n---@async\nfunction Channel:receive()\n    assert(not self.is_closed, \"Channel is closed.\")\n    while self.buffer == nil do\n        self.condvar:wait()\n    end\n    local value = self.buffer\n    self.buffer = nil\n    self.condvar:notify()\n    return value\nend\n\n---@async\nfunction Channel:iter()\n    return function()\n        while not self.is_closed do\n            return self:receive()\n        end\n    end\nend\n\nreturn {\n    Condvar = Condvar,\n    Semaphore = Semaphore,\n    OneShotChannel = OneShotChannel,\n    Channel = Channel,\n}\n"
  },
  {
    "path": "lua/mason-core/async/init.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal co = coroutine\n\nlocal exports = {}\n\nlocal Promise = {}\nPromise.__index = Promise\n\nfunction Promise:new(resolver)\n    local instance = {}\n    setmetatable(instance, self)\n    instance.resolver = resolver\n    instance.has_resolved = false\n    return instance\nend\n\n---@param success boolean\n---@param cb fun(success: boolean, value: table)\nfunction Promise:_wrap_resolver_cb(success, cb)\n    return function(...)\n        if self.has_resolved then\n            return\n        end\n        self.has_resolved = true\n        cb(success, { ... })\n    end\nend\n\nfunction Promise:__call(callback)\n    self.resolver(self:_wrap_resolver_cb(true, callback), self:_wrap_resolver_cb(false, callback))\nend\n\nlocal function await(resolver)\n    local ok, value = co.yield(Promise:new(resolver))\n    if not ok then\n        error(value[1], 0)\n    end\n    return unpack(value)\nend\n\nlocal function table_pack(...)\n    return { n = select(\"#\", ...), ... }\nend\n\n---@generic T\n---@param async_fn T\n---@param should_reject_err boolean? Whether the provided async_fn takes a callback with the signature `fun(err, result)`\n---@return T\nlocal function promisify(async_fn, should_reject_err)\n    return function(...)\n        local args = table_pack(...)\n        return await(function(resolve, reject)\n            if should_reject_err then\n                args[args.n + 1] = function(err, result)\n                    if err then\n                        reject(err)\n                    else\n                        resolve(result)\n                    end\n                end\n            else\n                args[args.n + 1] = resolve\n            end\n            local ok, err = pcall(async_fn, unpack(args, 1, args.n + 1))\n            if not ok then\n                reject(err)\n            end\n        end)\n    end\nend\n\nlocal function new_execution_context(suspend_fn, callback, ...)\n    ---@type thread?\n    local thread = co.create(suspend_fn)\n    local cancelled = false\n    local step\n    step = function(...)\n        if cancelled or not thread then\n            return\n        end\n        local ok, promise_or_result = co.resume(thread, ...)\n        if cancelled or not thread then\n            return\n        end\n        if ok then\n            if co.status(thread) == \"suspended\" then\n                if getmetatable(promise_or_result) == Promise then\n                    promise_or_result(step)\n                else\n                    -- yield to parent coroutine\n                    step(coroutine.yield(promise_or_result))\n                end\n            else\n                callback(true, promise_or_result)\n                thread = nil\n            end\n        else\n            callback(false, promise_or_result)\n            thread = nil\n        end\n    end\n\n    step(...)\n    return function()\n        cancelled = true\n        thread = nil\n    end\nend\n\nexports.run = function(suspend_fn, callback, ...)\n    return new_execution_context(suspend_fn, callback, ...)\nend\n\n---@generic T\n---@param suspend_fn T\nexports.scope = function(suspend_fn)\n    return function(...)\n        return new_execution_context(suspend_fn, function(success, err)\n            if not success then\n                error(err, 0)\n            end\n        end, ...)\n    end\nend\n\nexports.run_blocking = function(suspend_fn, ...)\n    local resolved, ok, result\n    local cancel_coroutine = new_execution_context(suspend_fn, function(a, b)\n        resolved = true\n        ok = a\n        result = b\n    end, ...)\n\n    if resolved or vim.wait(0x7FFFFFFF, function()\n        return resolved == true\n    end, 50) then\n        if not ok then\n            error(result, 2)\n        end\n        return result\n    else\n        cancel_coroutine()\n        error(\"async function failed to resolve in time.\", 2)\n    end\nend\n\nexports.wait = await\nexports.promisify = promisify\n\nexports.sleep = function(ms)\n    await(function(resolve)\n        vim.defer_fn(resolve, ms)\n    end)\nend\n\nexports.scheduler = function()\n    if vim.in_fast_event() then\n        await(vim.schedule)\n    end\nend\n\n---@async\n---@param suspend_fns async fun()[]\n---@param mode '\"first\"' | '\"all\"'\nlocal function wait(suspend_fns, mode)\n    local channel = require(\"mason-core.async.control\").OneShotChannel:new()\n    if #suspend_fns == 0 then\n        return\n    end\n\n    do\n        local results = {}\n        local thread_cancellations = {}\n        local count = #suspend_fns\n        local completed = 0\n\n        local function cancel()\n            for _, cancel_thread in ipairs(thread_cancellations) do\n                cancel_thread()\n            end\n        end\n\n        for i, suspend_fn in ipairs(suspend_fns) do\n            thread_cancellations[i] = exports.run(suspend_fn, function(success, result)\n                completed = completed + 1\n                if channel:is_closed() then\n                    return\n                end\n                if not success then\n                    cancel()\n                    channel:send(false, result)\n                    results = nil\n                    thread_cancellations = {}\n                else\n                    results[i] = result\n                    if mode == \"first\" or completed >= count then\n                        cancel()\n                        channel:send(true, mode == \"first\" and { result } or results)\n                        results = nil\n                        thread_cancellations = {}\n                    end\n                end\n            end)\n        end\n    end\n\n    local ok, results = channel:receive()\n    if not ok then\n        error(results, 2)\n    end\n    return unpack(results)\nend\n\n---@async\n---@param suspend_fns async fun()[]\nfunction exports.wait_all(suspend_fns)\n    return wait(suspend_fns, \"all\")\nend\n\n---@async\n---@param suspend_fns async fun()[]\nfunction exports.wait_first(suspend_fns)\n    return wait(suspend_fns, \"first\")\nend\n\nfunction exports.blocking(suspend_fn)\n    return _.partial(exports.run_blocking, suspend_fn)\nend\n\nreturn exports\n"
  },
  {
    "path": "lua/mason-core/async/uv.lua",
    "content": "local a = require \"mason-core.async\"\n\n---@type table<UvMethod, async fun(...)>\nlocal M = setmetatable({}, {\n    __index = function(cache, method)\n        cache[method] = a.promisify(vim.loop[method], true)\n        return cache[method]\n    end,\n})\n\nreturn M\n\n---@alias UvMethod\n---| '\"write\"'\n---| '\"shutdown\"'\n---| '\"close\"'\n---| '\"fs_close\"'\n---| '\"fs_open\"'\n---| '\"fs_read\"'\n---| '\"fs_unlink\"'\n---| '\"fs_write\"'\n---| '\"fs_mkdir\"'\n---| '\"fs_mkdtemp\"'\n---| '\"fs_mkstemp\"'\n---| '\"fs_rmdir\"'\n---| '\"fs_scandir\"'\n---| '\"fs_stat\"'\n---| '\"fs_fstat\"'\n---| '\"fs_lstat\"'\n---| '\"fs_rename\"'\n---| '\"fs_fsync\"'\n---| '\"fs_fdatasync\"'\n---| '\"fs_ftruncate\"'\n---| '\"fs_sendfile\"'\n---| '\"fs_access\"'\n---| '\"fs_chmod\"'\n---| '\"fs_fchmod\"'\n---| '\"fs_utime\"'\n---| '\"fs_futime\"'\n---| '\"fs_lutime\"'\n---| '\"fs_link\"'\n---| '\"fs_symlink\"'\n---| '\"fs_readlink\"'\n---| '\"fs_realpath\"'\n---| '\"fs_chown\"'\n---| '\"fs_fchown\"'\n---| '\"fs_lchown\"'\n---| '\"fs_copyfile\"'\n---| '\"fs_opendir\"'\n---| '\"fs_readdir\"'\n---| '\"fs_closedir\"'\n---| '\"fs_statfs\"'\n"
  },
  {
    "path": "lua/mason-core/fetch.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal async_uv = require \"mason-core.async.uv\"\nlocal log = require \"mason-core.log\"\nlocal platform = require \"mason-core.platform\"\nlocal powershell = require \"mason-core.installer.managers.powershell\"\nlocal spawn = require \"mason-core.spawn\"\nlocal version = require \"mason.version\"\n\nlocal USER_AGENT = (\"mason.nvim %s (+https://github.com/mason-org/mason.nvim)\"):format(version.VERSION)\n\nlocal TIMEOUT_SECONDS = 30\n\n---@alias FetchMethod\n---| '\"GET\"'\n---| '\"POST\"'\n---| '\"PUT\"'\n---| '\"PATCH\"'\n---| '\"DELETE\"'\n\n---@alias FetchOpts {out_file: string?, method: FetchMethod?, headers: table<string, string>?, data: string?}\n\n---@async\n---@param url string The url to fetch.\n---@param opts FetchOpts?\n---@return Result # Result<string>\nlocal function fetch(url, opts)\n    opts = opts or {}\n    if not opts.headers then\n        opts.headers = {}\n    end\n    if not opts.method then\n        opts.method = \"GET\"\n    end\n    opts.headers[\"User-Agent\"] = USER_AGENT\n    log.fmt_debug(\"Fetching URL %s\", url)\n\n    local platform_specific = Result.failure\n\n    if platform.is.win then\n        local header_entries = _.join(\n            \"; \",\n            _.map(function(pair)\n                return (\"%q=%q\"):format(pair[1], pair[2])\n            end, _.to_pairs(opts.headers))\n        )\n        local headers = (\"-Headers @{%s}\"):format(header_entries)\n        if opts.out_file then\n            platform_specific = function()\n                return powershell.command(\n                    ([[iwr %s -TimeoutSec %s -UseBasicParsing -Method %q -Uri %q %s -OutFile %q;]]):format(\n                        headers,\n                        TIMEOUT_SECONDS,\n                        opts.method,\n                        url,\n                        opts.data and (\"-Body %s\"):format(opts.data) or \"\",\n                        opts.out_file\n                    )\n                )\n            end\n        else\n            platform_specific = function()\n                return powershell.command(\n                    ([[Write-Output (iwr %s -TimeoutSec %s -Method %q -UseBasicParsing %s -Uri %q).Content;]]):format(\n                        headers,\n                        TIMEOUT_SECONDS,\n                        opts.method,\n                        opts.data and (\"-Body %s\"):format(opts.data) or \"\",\n                        url\n                    )\n                )\n            end\n        end\n    end\n\n    local function wget()\n        local headers = _.sort_by(\n            _.nth(2),\n            _.map(\n                _.compose(function(header)\n                    return { \"--header\", header }\n                end, _.join \": \"),\n                _.to_pairs(opts.headers)\n            )\n        )\n\n        if opts.data and opts.method ~= \"POST\" then\n            return Result.failure((\"fetch: data provided but method is not POST (was %s)\"):format(opts.method or \"-\"))\n        end\n\n        if not _.any(_.equals(opts.method), { \"GET\", \"POST\" }) then\n            -- Note: --spider can be used for HEAD support, if ever needed\n            return Result.failure((\"fetch: wget doesn't support HTTP method %s\"):format(opts.method))\n        end\n\n        return spawn.wget {\n            headers,\n            \"-o\",\n            \"/dev/null\",\n            \"-O\",\n            opts.out_file or \"-\",\n            \"-T\",\n            TIMEOUT_SECONDS,\n            opts.data and opts.method == \"POST\" and {\n                \"--post-data\",\n                opts.data,\n            } or vim.NIL,\n            url,\n        }\n    end\n\n    local function curl()\n        local headers = _.sort_by(\n            _.nth(2),\n            _.map(\n                _.compose(function(header)\n                    return { \"-H\", header }\n                end, _.join \": \"),\n                _.to_pairs(opts.headers)\n            )\n        )\n        return spawn.curl {\n            headers,\n            \"-fsSL\",\n            {\n                \"-X\",\n                opts.method,\n            },\n            opts.data and { \"-d\", \"@-\" } or vim.NIL,\n            opts.out_file and { \"-o\", opts.out_file } or vim.NIL,\n            { \"--connect-timeout\", TIMEOUT_SECONDS },\n            url,\n            on_spawn = a.scope(function(_, stdio)\n                local stdin = stdio[1]\n                if opts.data then\n                    log.trace(\"Writing stdin to curl\", opts.data)\n                    async_uv.write(stdin, opts.data)\n                end\n                async_uv.shutdown(stdin)\n                async_uv.close(stdin)\n            end),\n        }\n    end\n\n    return curl():or_else(wget):or_else(platform_specific):map(function(result)\n        if opts.out_file then\n            return result\n        else\n            return result.stdout\n        end\n    end)\nend\n\nreturn fetch\n"
  },
  {
    "path": "lua/mason-core/fs.lua",
    "content": "local Path = require \"mason-core.path\"\nlocal a = require \"mason-core.async\"\nlocal log = require \"mason-core.log\"\nlocal settings = require \"mason.settings\"\n\nlocal function make_module(uv)\n    local M = {}\n\n    ---@param path string\n    function M.fstat(path)\n        log.trace(\"fs: fstat\", path)\n        local fd = uv.fs_open(path, \"r\", 438)\n        local fstat = uv.fs_fstat(fd)\n        uv.fs_close(fd)\n        return fstat\n    end\n\n    ---@param path string\n    function M.file_exists(path)\n        log.trace(\"fs: file_exists\", path)\n        local ok, fstat = pcall(M.fstat, path)\n        if not ok then\n            return false\n        end\n        return fstat.type == \"file\"\n    end\n\n    ---@param path string\n    function M.dir_exists(path)\n        log.trace(\"fs: dir_exists\", path)\n        local ok, fstat = pcall(M.fstat, path)\n        if not ok then\n            return false\n        end\n        return fstat.type == \"directory\"\n    end\n\n    ---@param path string\n    function M.rmrf(path)\n        assert(\n            Path.is_subdirectory(settings.current.install_root_dir, path),\n            (\"Refusing to rmrf %q which is outside of the allowed boundary %q. Please report this error at https://github.com/mason-org/mason.nvim/issues/new\"):format(\n                path,\n                settings.current.install_root_dir\n            )\n        )\n        log.debug(\"fs: rmrf\", path)\n        if vim.in_fast_event() then\n            a.scheduler()\n        end\n        if vim.fn.delete(path, \"rf\") ~= 0 then\n            log.debug \"fs: rmrf failed\"\n            error((\"rmrf: Could not remove directory %q.\"):format(path))\n        end\n    end\n\n    ---@param path string\n    function M.unlink(path)\n        log.debug(\"fs: unlink\", path)\n        uv.fs_unlink(path)\n    end\n\n    ---@param path string\n    function M.mkdir(path)\n        log.debug(\"fs: mkdir\", path)\n        uv.fs_mkdir(path, 493) -- 493(10) == 755(8)\n    end\n\n    ---@param path string\n    function M.mkdirp(path)\n        log.debug(\"fs: mkdirp\", path)\n        if vim.in_fast_event() then\n            a.scheduler()\n        end\n        if vim.fn.mkdir(path, \"p\") ~= 1 then\n            log.debug \"fs: mkdirp failed\"\n            error((\"mkdirp: Could not create directory %q.\"):format(path))\n        end\n    end\n\n    ---@param path string\n    function M.rmdir(path)\n        log.debug(\"fs: rmdir\", path)\n        uv.fs_rmdir(path)\n    end\n\n    ---@param path string\n    ---@param new_path string\n    function M.rename(path, new_path)\n        log.debug(\"fs: rename\", path, new_path)\n        uv.fs_rename(path, new_path)\n    end\n\n    ---@param path string\n    ---@param new_path string\n    ---@param flags table? { excl?: boolean, ficlone?: boolean, ficlone_force?: boolean }\n    function M.copy_file(path, new_path, flags)\n        log.debug(\"fs: copy_file\", path, new_path, flags)\n        uv.fs_copyfile(path, new_path, flags)\n    end\n\n    ---@param path string\n    ---@param contents string\n    ---@param flags string? Defaults to \"w\".\n    function M.write_file(path, contents, flags)\n        log.trace(\"fs: write_file\", path)\n        local fd = uv.fs_open(path, flags or \"w\", 438)\n        uv.fs_write(fd, contents, -1)\n        uv.fs_close(fd)\n    end\n\n    ---@param path string\n    ---@param contents string\n    function M.append_file(path, contents)\n        M.write_file(path, contents, \"a\")\n    end\n\n    ---@param path string\n    function M.read_file(path)\n        log.trace(\"fs: read_file\", path)\n        local fd = uv.fs_open(path, \"r\", 438)\n        local fstat = uv.fs_fstat(fd)\n        local contents = uv.fs_read(fd, fstat.size, 0)\n        uv.fs_close(fd)\n        return contents\n    end\n\n    ---@alias ReaddirEntry {name: string, type: string}\n\n    ---@param path string: The full path to the directory to read.\n    ---@return ReaddirEntry[]\n    function M.readdir(path)\n        log.trace(\"fs: fs_opendir\", path)\n        local dir = assert(vim.loop.fs_opendir(path, nil, 25))\n        local all_entries = {}\n        local exhausted = false\n\n        repeat\n            local entries = uv.fs_readdir(dir)\n            log.trace(\"fs: fs_readdir\", path, entries)\n            if entries and #entries > 0 then\n                for i = 1, #entries do\n                    if entries[i].name and not entries[i].type then\n                        -- See https://github.com/luvit/luv/issues/660\n                        local full_path = Path.concat { path, entries[i].name }\n                        log.trace(\"fs: fs_readdir falling back to fs_stat to find type\", full_path)\n                        local stat = uv.fs_stat(full_path)\n                        entries[i].type = stat.type\n                    end\n                    all_entries[#all_entries + 1] = entries[i]\n                end\n            else\n                log.trace(\"fs: fs_readdir exhausted scan\", path)\n                exhausted = true\n            end\n        until exhausted\n\n        uv.fs_closedir(dir)\n\n        return all_entries\n    end\n\n    ---@param path string\n    ---@param new_path string\n    function M.symlink(path, new_path)\n        log.trace(\"fs: symlink\", path, new_path)\n        uv.fs_symlink(path, new_path)\n    end\n\n    ---@param path string\n    ---@param mode integer\n    function M.chmod(path, mode)\n        log.trace(\"fs: chmod\", path, mode)\n        uv.fs_chmod(path, mode)\n    end\n\n    return M\nend\n\nreturn {\n    async = make_module(require \"mason-core.async.uv\"),\n    sync = make_module(vim.loop),\n}\n"
  },
  {
    "path": "lua/mason-core/functional/data.lua",
    "content": "local _ = {}\n\n_.table_pack = function(...)\n    return { n = select(\"#\", ...), ... }\nend\n\n---@generic T : string\n---@param values T[]\n---@return table<T, T>\n_.enum = function(values)\n    local result = {}\n    for i = 1, #values do\n        local v = values[i]\n        result[v] = v\n    end\n    return result\nend\n\n---@generic T\n---@param list T[]\n---@return table<T, boolean>\n_.set_of = function(list)\n    local set = {}\n    for i = 1, #list do\n        set[list[i]] = true\n    end\n    return set\nend\n\nreturn _\n"
  },
  {
    "path": "lua/mason-core/functional/function.lua",
    "content": "local data = require \"mason-core.functional.data\"\n\nlocal _ = {}\n\n---@generic T : fun(...)\n---@param fn T\n---@param arity integer\n---@return T\n_.curryN = function(fn, arity)\n    return function(...)\n        local args = data.table_pack(...)\n        if args.n >= arity then\n            return fn(unpack(args, 1, arity))\n        else\n            return _.curryN(_.partial(fn, unpack(args, 1, args.n)), arity - args.n)\n        end\n    end\nend\n\n_.compose = function(...)\n    local functions = data.table_pack(...)\n    assert(functions.n > 0, \"compose requires at least one function\")\n    return function(...)\n        local result = data.table_pack(...)\n        for i = functions.n, 1, -1 do\n            result = data.table_pack(functions[i](unpack(result, 1, result.n)))\n        end\n        return unpack(result, 1, result.n)\n    end\nend\n\n---@generic T\n---@param fn fun(...): T\n---@return fun(...): T\n_.partial = function(fn, ...)\n    local bound_args = data.table_pack(...)\n    return function(...)\n        local args = data.table_pack(...)\n        local merged_args = {}\n        for i = 1, bound_args.n do\n            merged_args[i] = bound_args[i]\n        end\n        for i = 1, args.n do\n            merged_args[bound_args.n + i] = args[i]\n        end\n        return fn(unpack(merged_args, 1, bound_args.n + args.n))\n    end\nend\n\n---@generic T\n---@param value T\n---@return T\n_.identity = function(value)\n    return value\nend\n\n_.always = function(a)\n    return function()\n        return a\n    end\nend\n\n_.T = _.always(true)\n_.F = _.always(false)\n\n---@generic T : fun(...)\n---@param fn T\n---@param cache_key_generator (fun(...): any)?\n---@return T\n_.memoize = function(fn, cache_key_generator)\n    cache_key_generator = cache_key_generator or _.identity\n    local cache = {}\n    return function(...)\n        local key = cache_key_generator(...)\n        if not cache[key] then\n            cache[key] = data.table_pack(fn(...))\n        end\n        return unpack(cache[key], 1, cache[key].n)\n    end\nend\n\n---@generic T\n---@param fn fun(): T\n---@return fun(): T\n_.lazy = function(fn)\n    local memoized = _.memoize(fn, _.always \"lazyval\")\n    return function()\n        return memoized()\n    end\nend\n\n_.tap = _.curryN(function(fn, value)\n    fn(value)\n    return value\nend, 2)\n\n---@generic T, U\n---@param value T\n---@param fn fun(value: T): U\n---@return U\n_.apply_to = _.curryN(function(value, fn)\n    return fn(value)\nend, 2)\n\n---@generic T, R, V\n---@param fn fun (args...: V[]): R\n---@param args V[]\n---@return R\n_.apply = _.curryN(function(fn, args)\n    return fn(unpack(args))\nend, 2)\n\n---@generic T, V\n---@param fn fun(...): T\n---@param fns (fun(value: V))[]\n---@param val V\n---@return T\n_.converge = _.curryN(function(fn, fns, val)\n    return fn(unpack(vim.tbl_map(_.apply_to(val), fns)))\nend, 3)\n\n---@param spec table\n---@param value any\n---@return table\n_.apply_spec = _.curryN(function(spec, value)\n    spec = vim.deepcopy(spec)\n    local function transform(item)\n        if type(item) == \"table\" then\n            for k, v in pairs(item) do\n                item[k] = transform(v)\n            end\n            return item\n        else\n            return item(value)\n        end\n    end\n    return transform(spec)\nend, 2)\n\nreturn _\n"
  },
  {
    "path": "lua/mason-core/functional/init.lua",
    "content": "local _ = {}\n\nlocal function lazy_require(module)\n    return setmetatable({}, {\n        __index = function(m, k)\n            return require(module)[k]\n        end,\n    })\nend\n\n_.lazy_require = lazy_require\n\n---@module \"mason-core.functional.data\"\nlocal data = lazy_require \"mason-core.functional.data\"\n_.table_pack = data.table_pack\n_.enum = data.enum\n_.set_of = data.set_of\n\n---@module \"mason-core.functional.function\"\nlocal fun = lazy_require \"mason-core.functional.function\"\n_.curryN = fun.curryN\n_.compose = fun.compose\n_.partial = fun.partial\n_.identity = fun.identity\n_.always = fun.always\n_.T = fun.T\n_.F = fun.F\n_.memoize = fun.memoize\n_.lazy = fun.lazy\n_.tap = fun.tap\n_.apply_to = fun.apply_to\n_.apply = fun.apply\n_.converge = fun.converge\n_.apply_spec = fun.apply_spec\n\n---@module \"mason-core.functional.list\"\nlocal list = lazy_require \"mason-core.functional.list\"\n_.reverse = list.reverse\n_.list_not_nil = list.list_not_nil\n_.list_copy = list.list_copy\n_.find_first = list.find_first\n_.any = list.any\n_.all = list.all\n_.filter = list.filter\n_.map = list.map\n_.filter_map = list.filter_map\n_.each = list.each\n_.concat = list.concat\n_.append = list.append\n_.prepend = list.prepend\n_.zip_table = list.zip_table\n_.nth = list.nth\n_.head = list.head\n_.last = list.last\n_.length = list.length\n_.flatten = list.flatten\n_.sort_by = list.sort_by\n_.uniq_by = list.uniq_by\n_.join = list.join\n_.partition = list.partition\n_.take = list.take\n_.drop = list.drop\n_.drop_last = list.drop_last\n_.reduce = list.reduce\n_.split_every = list.split_every\n_.index_by = list.index_by\n\n---@module \"mason-core.functional.relation\"\nlocal relation = lazy_require \"mason-core.functional.relation\"\n_.equals = relation.equals\n_.not_equals = relation.not_equals\n_.prop_eq = relation.prop_eq\n_.prop_satisfies = relation.prop_satisfies\n_.path_satisfies = relation.path_satisfies\n_.min = relation.min\n_.add = relation.add\n\n---@module \"mason-core.functional.logic\"\nlocal logic = lazy_require \"mason-core.functional.logic\"\n_.all_pass = logic.all_pass\n_.any_pass = logic.any_pass\n_.if_else = logic.if_else\n_.is_not = logic.is_not\n_.complement = logic.complement\n_.cond = logic.cond\n_.default_to = logic.default_to\n\n---@module \"mason-core.functional.number\"\nlocal number = lazy_require \"mason-core.functional.number\"\n_.negate = number.negate\n_.gt = number.gt\n_.gte = number.gte\n_.lt = number.lt\n_.lte = number.lte\n_.inc = number.inc\n_.dec = number.dec\n\n---@module \"mason-core.functional.string\"\nlocal string = lazy_require \"mason-core.functional.string\"\n_.matches = string.matches\n_.match = string.match\n_.format = string.format\n_.split = string.split\n_.gsub = string.gsub\n_.trim = string.trim\n_.trim_start_matches = string.trim_start_matches\n_.trim_end_matches = string.trim_end_matches\n_.strip_prefix = string.strip_prefix\n_.strip_suffix = string.strip_suffix\n_.dedent = string.dedent\n_.starts_with = string.starts_with\n_.to_upper = string.to_upper\n_.to_lower = string.to_lower\n\n---@module \"mason-core.functional.table\"\nlocal tbl = lazy_require \"mason-core.functional.table\"\n_.prop = tbl.prop\n_.path = tbl.path\n_.pick = tbl.pick\n_.keys = tbl.keys\n_.size = tbl.size\n_.to_pairs = tbl.to_pairs\n_.from_pairs = tbl.from_pairs\n_.invert = tbl.invert\n_.evolve = tbl.evolve\n_.merge_left = tbl.merge_left\n_.dissoc = tbl.dissoc\n_.assoc = tbl.assoc\n\n---@module \"mason-core.functional.type\"\nlocal typ = lazy_require \"mason-core.functional.type\"\n_.is_nil = typ.is_nil\n_.is = typ.is\n_.is_list = typ.is_list\n\n-- TODO do something else with these\n\n_.coalesce = function(...)\n    local args = _.table_pack(...)\n    for i = 1, args.n do\n        local variable = args[i]\n        if variable ~= nil then\n            return variable\n        end\n    end\nend\n\n_.when = function(condition, value)\n    return condition and value or nil\nend\n\n_.lazy_when = function(condition, value)\n    return condition and value() or nil\nend\n\n---@param fn fun()\n_.scheduler = function(fn)\n    if vim.in_fast_event() then\n        vim.schedule(fn)\n    else\n        fn()\n    end\nend\n\n---@generic T : fun(...)\n---@param fn T\n---@return T\n_.scheduler_wrap = function(fn)\n    return function(...)\n        local args = _.table_pack(...)\n        _.scheduler(function()\n            fn(unpack(args, 1, args.n + 1))\n        end)\n    end\nend\n\nreturn _\n"
  },
  {
    "path": "lua/mason-core/functional/list.lua",
    "content": "local data = require \"mason-core.functional.data\"\nlocal fun = require \"mason-core.functional.function\"\n\nlocal _ = {}\n\n---@generic T\n---@param list T[]\n---@return T[]\n_.reverse = function(list)\n    local result = {}\n    for i = #list, 1, -1 do\n        result[#result + 1] = list[i]\n    end\n    return result\nend\n\n_.list_not_nil = function(...)\n    local result = {}\n    local args = data.table_pack(...)\n    for i = 1, args.n do\n        if args[i] ~= nil then\n            result[#result + 1] = args[i]\n        end\n    end\n    return result\nend\n\n---@generic T\n---@param predicate fun(item: T): boolean\n---@param list T[]\n---@return T | nil\n_.find_first = fun.curryN(function(predicate, list)\n    local result\n    for i = 1, #list do\n        local entry = list[i]\n        if predicate(entry) then\n            return entry\n        end\n    end\n    return result\nend, 2)\n\n---@generic T\n---@param predicate fun(item: T): boolean\n---@param list T[]\n---@return boolean\n_.any = fun.curryN(function(predicate, list)\n    for i = 1, #list do\n        if predicate(list[i]) then\n            return true\n        end\n    end\n    return false\nend, 2)\n\n---@generic T\n---@param predicate fun(item: T): boolean\n---@param list T[]\n---@return boolean\n_.all = fun.curryN(function(predicate, list)\n    for i = 1, #list do\n        if not predicate(list[i]) then\n            return false\n        end\n    end\n    return true\nend, 2)\n\n---@generic T\n---@type fun(filter_fn: (fun(item: T): boolean), items: T[]): T[]\n_.filter = fun.curryN(vim.tbl_filter, 2)\n\n---@generic T, U\n---@type fun(map_fn: (fun(item: T): U), items: T[]): U[]\n_.map = fun.curryN(vim.tbl_map, 2)\n\n---@param tbl table\n---@return table\n_.flatten = function(tbl)\n    local result = {}\n    --- @param _tbl table<any,any>\n    local function _tbl_flatten(_tbl)\n        local n = #_tbl\n        for i = 1, n do\n            local v = _tbl[i]\n            if type(v) == \"table\" then\n                _tbl_flatten(v)\n            elseif v then\n                table.insert(result, v)\n            end\n        end\n    end\n    _tbl_flatten(tbl)\n    return result\nend\n\n---@generic T\n---@param map_fn fun(item: T): Optional\n---@param list T[]\n---@return any[]\n_.filter_map = fun.curryN(function(map_fn, list)\n    local ret = {}\n    for i = 1, #list do\n        map_fn(list[i]):if_present(function(value)\n            ret[#ret + 1] = value\n        end)\n    end\n    return ret\nend, 2)\n\n---@generic T\n---@param fn fun(item: T, index: integer)\n---@param list T[]\n_.each = fun.curryN(function(fn, list)\n    for k, v in pairs(list) do\n        fn(v, k)\n    end\nend, 2)\n\n---@generic T\n---@type fun(list: T[]): T[]\n_.list_copy = _.map(fun.identity)\n\n_.concat = fun.curryN(function(a, b)\n    if type(a) == \"table\" then\n        assert(type(b) == \"table\", \"concat: expected table\")\n        return vim.list_extend(_.list_copy(a), b)\n    elseif type(a) == \"string\" then\n        assert(type(b) == \"string\", \"concat: expected string\")\n        return a .. b\n    end\nend, 2)\n\n---@generic T\n---@param value T\n---@param list T[]\n---@return T[]\n_.append = fun.curryN(function(value, list)\n    local list_copy = _.list_copy(list)\n    list_copy[#list_copy + 1] = value\n    return list_copy\nend, 2)\n\n---@generic T\n---@param value T\n---@param list T[]\n---@return T[]\n_.prepend = fun.curryN(function(value, list)\n    local list_copy = _.list_copy(list)\n    table.insert(list_copy, 1, value)\n    return list_copy\nend, 2)\n\n---@generic T\n---@generic U\n---@param keys T[]\n---@param values U[]\n---@return table<T, U>\n_.zip_table = fun.curryN(function(keys, values)\n    local res = {}\n    for i, key in ipairs(keys) do\n        res[key] = values[i]\n    end\n    return res\nend, 2)\n\n---@generic T\n---@param offset number\n---@param value T[]|string\n---@return T|string|nil\n_.nth = fun.curryN(function(offset, value)\n    local index = offset < 0 and (#value + (offset + 1)) or offset\n    if type(value) == \"string\" then\n        return string.sub(value, index, index)\n    else\n        return value[index]\n    end\nend, 2)\n\n_.head = _.nth(1)\n\n---@generic T\n---@param list T[]\n---@return T?\n_.last = function(list)\n    return list[#list]\nend\n\n---@param value string|any[]\n_.length = function(value)\n    return #value\nend\n\n---@generic T\n---@param comp fun(item: T): any\n---@param list T[]\n---@return T[]\n_.sort_by = fun.curryN(function(comp, list)\n    local copied_list = _.list_copy(list)\n    table.sort(copied_list, function(a, b)\n        return comp(a) < comp(b)\n    end)\n    return copied_list\nend, 2)\n\n---@param sep string\n---@param list any[]\n_.join = fun.curryN(function(sep, list)\n    return table.concat(list, sep)\nend, 2)\n\n---@generic T\n---@param id fun(item: T): any\n---@param list T[]\n---@return T[]\n_.uniq_by = fun.curryN(function(id, list)\n    local set = {}\n    local result = {}\n    for i = 1, #list do\n        local item = list[i]\n        local uniq_key = id(item)\n        if not set[uniq_key] then\n            set[uniq_key] = true\n            table.insert(result, item)\n        end\n    end\n    return result\nend, 2)\n\n---@generic T\n---@param predicate fun(item: T): boolean\n---@param list T[]\n---@return T[][] # [T[], T[]]\n_.partition = fun.curryN(function(predicate, list)\n    local partitions = { {}, {} }\n    for _, item in ipairs(list) do\n        table.insert(partitions[predicate(item) and 1 or 2], item)\n    end\n    return partitions\nend, 2)\n\n---@generic T\n---@param n integer\n---@param list T[]\n---@return T[]\n_.take = fun.curryN(function(n, list)\n    local result = {}\n    for i = 1, math.min(n, #list) do\n        result[#result + 1] = list[i]\n    end\n    return result\nend, 2)\n\n---@generic T\n---@param n integer\n---@param list T[]\n---@return T[]\n_.drop = fun.curryN(function(n, list)\n    local result = {}\n    for i = n + 1, #list do\n        result[#result + 1] = list[i]\n    end\n    return result\nend, 2)\n\n---@generic T\n---@param n integer\n---@param list T[]\n---@return T[]\n_.drop_last = fun.curryN(function(n, list)\n    local result = {}\n    for i = 1, #list - n do\n        result[#result + 1] = list[i]\n    end\n    return result\nend, 2)\n\n---@generic T, U\n---@param fn fun(acc: U, item: T): U\n---@param acc U\n---@param list T[]\n---@return U\n_.reduce = fun.curryN(function(fn, acc, list)\n    for i = 1, #list do\n        acc = fn(acc, list[i])\n    end\n    return acc\nend, 3)\n\n---@generic T\n---@param n integer\n---@param list T[]\n---@return T[][]\n_.split_every = fun.curryN(function(n, list)\n    assert(n > 0, \"n needs to be greater than 0.\")\n    local res = {}\n    local idx = 1\n    while idx <= #list do\n        table.insert(res, { unpack(list, idx, idx + n - 1) })\n        idx = idx + n\n    end\n    return res\nend, 2)\n\n---@generic T, U\n---@param index fun(item: T): U\n---@param list T[]\n---@return table<U, T>\n_.index_by = fun.curryN(function(index, list)\n    local res = {}\n    for _, item in ipairs(list) do\n        res[index(item)] = item\n    end\n    return res\nend, 2)\n\nreturn _\n"
  },
  {
    "path": "lua/mason-core/functional/logic.lua",
    "content": "local fun = require \"mason-core.functional.function\"\n\nlocal _ = {}\n\n---@generic T\n---@param predicates (fun(item: T): boolean)[]\n---@param item T\n---@return boolean\n_.all_pass = fun.curryN(function(predicates, item)\n    for i = 1, #predicates do\n        if not predicates[i](item) then\n            return false\n        end\n    end\n    return true\nend, 2)\n\n---@generic T\n---@param predicates (fun(item: T): boolean)[]\n---@param item T\n---@return boolean\n_.any_pass = fun.curryN(function(predicates, item)\n    for i = 1, #predicates do\n        if predicates[i](item) then\n            return true\n        end\n    end\n    return false\nend, 2)\n\n---@generic T, U\n---@param predicate fun(item: T): boolean\n---@param on_true fun(item: T): U\n---@param on_false fun(item: T): U\n---@param value T\n---@return U\n_.if_else = fun.curryN(function(predicate, on_true, on_false, value)\n    if predicate(value) then\n        return on_true(value)\n    else\n        return on_false(value)\n    end\nend, 4)\n\n---@param value boolean\n---@return boolean\n_.is_not = function(value)\n    return not value\nend\n\n---@generic T\n---@param predicate fun(value: T): boolean\n---@param value T\n---@return boolean\n_.complement = fun.curryN(function(predicate, value)\n    return not predicate(value)\nend, 2)\n\n---@generic T, U\n---@param predicate_transformer_pairs {[1]: (fun(value: T): boolean), [2]: (fun(value: T): U)}[]\n---@param value T\n---@return U?\n_.cond = fun.curryN(function(predicate_transformer_pairs, value)\n    for _, pair in ipairs(predicate_transformer_pairs) do\n        local predicate, transformer = pair[1], pair[2]\n        if predicate(value) then\n            return transformer(value)\n        end\n    end\nend, 2)\n\n---@generic T\n---@param default_val T\n---@param val T?\n---@return T\n_.default_to = fun.curryN(function(default_val, val)\n    if val ~= nil then\n        return val\n    else\n        return default_val\n    end\nend, 2)\n\nreturn _\n"
  },
  {
    "path": "lua/mason-core/functional/number.lua",
    "content": "local fun = require \"mason-core.functional.function\"\n\nlocal _ = {}\n\n---@param number number\n_.negate = function(number)\n    return -number\nend\n\n_.gt = fun.curryN(function(number, value)\n    return value > number\nend, 2)\n\n_.gte = fun.curryN(function(number, value)\n    return value >= number\nend, 2)\n\n_.lt = fun.curryN(function(number, value)\n    return value < number\nend, 2)\n\n_.lte = fun.curryN(function(number, value)\n    return value <= number\nend, 2)\n\n_.inc = fun.curryN(function(increment, value)\n    return value + increment\nend, 2)\n\n_.dec = fun.curryN(function(decrement, value)\n    return value - decrement\nend, 2)\n\nreturn _\n"
  },
  {
    "path": "lua/mason-core/functional/relation.lua",
    "content": "local fun = require \"mason-core.functional.function\"\n\nlocal _ = {}\n\n_.equals = fun.curryN(function(expected, value)\n    return value == expected\nend, 2)\n\n_.not_equals = fun.curryN(function(expected, value)\n    return value ~= expected\nend, 2)\n\n_.prop_eq = fun.curryN(function(property, value, tbl)\n    return tbl[property] == value\nend, 3)\n\n_.prop_satisfies = fun.curryN(function(predicate, property, tbl)\n    return predicate(tbl[property])\nend, 3)\n\n---@param predicate fun(value: any): boolean\n---@param path any[]\n---@param tbl table\n_.path_satisfies = fun.curryN(function(predicate, path, tbl)\n    -- see https://github.com/neovim/neovim/pull/21426\n    local value = vim.tbl_get(tbl, unpack(path))\n    return predicate(value)\nend, 3)\n\n---@param a number\n---@param b number\n---@return number\n_.min = fun.curryN(function(a, b)\n    return b - a\nend, 2)\n\n---@param a number\n---@param b number\n---@return number\n_.add = fun.curryN(function(a, b)\n    return b + a\nend, 2)\n\nreturn _\n"
  },
  {
    "path": "lua/mason-core/functional/string.lua",
    "content": "local fun = require \"mason-core.functional.function\"\n\nlocal _ = {}\n\n---@param pattern string\n---@param str string\n_.matches = fun.curryN(function(pattern, str)\n    return str:match(pattern) ~= nil\nend, 2)\n\n_.match = fun.curryN(function(pattern, str)\n    return { str:match(pattern) }\nend, 2)\n\n---@param template string\n---@param str string\n_.format = fun.curryN(function(template, str)\n    return template:format(str)\nend, 2)\n\n---@param sep string\n---@param str string\n_.split = fun.curryN(function(sep, str)\n    return vim.split(str, sep)\nend, 2)\n\n---@param pattern string\n---@param repl string|function|table\n---@param str string\n_.gsub = fun.curryN(function(pattern, repl, str)\n    return string.gsub(str, pattern, repl)\nend, 3)\n\n_.trim = fun.curryN(function(str)\n    return vim.trim(str)\nend, 1)\n\n---https://github.com/nvim-lua/nvim-package-specification/blob/93475e47545b579fd20b6c5ce13c4163e7956046/lua/packspec/schema.lua#L8-L37\n---@param str string\n---@return string\n_.dedent = fun.curryN(function(str)\n    local lines = {}\n    local indent = nil\n\n    for line in str:gmatch \"[^\\n]*\\n?\" do\n        if indent == nil then\n            if not line:match \"^%s*$\" then\n                -- save pattern for indentation from the first non-empty line\n                indent, line = line:match \"^(%s*)(.*)$\"\n                indent = \"^\" .. indent .. \"(.*)$\"\n                table.insert(lines, line)\n            end\n        else\n            if line:match \"^%s*$\" then\n                -- replace empty lines with a single newline character.\n                -- empty lines are handled separately to allow the\n                -- closing \"]]\" to be one indentation level lower.\n                table.insert(lines, \"\\n\")\n            else\n                -- strip indentation on non-empty lines\n                line = assert(line:match(indent), \"inconsistent indentation\")\n                table.insert(lines, line)\n            end\n        end\n    end\n\n    lines = table.concat(lines)\n    -- trim trailing whitespace\n    return lines:match \"^(.-)%s*$\"\nend, 1)\n\n---@param prefix string\n---@str string\n_.starts_with = fun.curryN(function(prefix, str)\n    return vim.startswith(str, prefix)\nend, 2)\n\n---@param str string\n_.to_upper = function(str)\n    return str:upper()\nend\n\n---@param str string\n_.to_lower = function(str)\n    return str:lower()\nend\n\n---@param pattern string\n---@param str string\n_.trim_start_matches = fun.curryN(function(pattern, str)\n    for i = 1, #str do\n        if not str:sub(i, i):match(pattern) then\n            return str:sub(i)\n        end\n    end\n    return str\nend, 2)\n\n---@param pattern string\n---@param str string\n_.trim_end_matches = fun.curryN(function(pattern, str)\n    for i = #str, 1, -1 do\n        if not str:sub(i, i):match(pattern) then\n            return str:sub(1, i)\n        end\n    end\n    return str\nend, 2)\n\n_.strip_prefix = fun.curryN(function(prefix_pattern, str)\n    if #prefix_pattern > #str then\n        return str\n    end\n    for i = 1, #prefix_pattern do\n        if str:sub(i, i) ~= prefix_pattern:sub(i, i) then\n            return str\n        end\n    end\n    return str:sub(#prefix_pattern + 1)\nend, 2)\n\n_.strip_suffix = fun.curryN(function(suffix_pattern, str)\n    if #suffix_pattern > #str then\n        return str\n    end\n    for i = 1, #suffix_pattern do\n        if str:sub(-i, -i) ~= suffix_pattern:sub(-i, -i) then\n            return str\n        end\n    end\n    return str:sub(1, -#suffix_pattern - 1)\nend, 2)\n\nreturn _\n"
  },
  {
    "path": "lua/mason-core/functional/table.lua",
    "content": "local fun = require \"mason-core.functional.function\"\n\nlocal _ = {}\n\n---@generic T : table\n---@param tbl T\n---@return T\nlocal function shallow_clone(tbl)\n    local res = {}\n    for k, v in pairs(tbl) do\n        res[k] = v\n    end\n    return res\nend\n\n---@generic T, U\n---@param index T\n---@param tbl table<T, U>\n---@return U?\n_.prop = fun.curryN(function(index, tbl)\n    return tbl[index]\nend, 2)\n\n---@param path any[]\n---@param tbl table\n_.path = fun.curryN(function(path, tbl)\n    -- see https://github.com/neovim/neovim/pull/21426\n    local value = vim.tbl_get(tbl, unpack(path))\n    return value\nend, 2)\n\n---@generic T, U\n---@param keys T[]\n---@param tbl table<T, U>\n---@return table<T, U>\n_.pick = fun.curryN(function(keys, tbl)\n    local ret = {}\n    for _, key in ipairs(keys) do\n        ret[key] = tbl[key]\n    end\n    return ret\nend, 2)\n\n_.keys = fun.curryN(vim.tbl_keys, 1)\n_.size = fun.curryN(vim.tbl_count, 1)\n\n---@generic K, V\n---@param tbl table<K, V>\n---@return { [1]: K, [2]: V }[]\n_.to_pairs = fun.curryN(function(tbl)\n    local result = {}\n    for k, v in pairs(tbl) do\n        result[#result + 1] = { k, v }\n    end\n    return result\nend, 1)\n\n---@generic K, V\n---@param pairs { [1]: K, [2]: V }[]\n---@return table<K, V>\n_.from_pairs = fun.curryN(function(pairs)\n    local result = {}\n    for _, pair in ipairs(pairs) do\n        result[pair[1]] = pair[2]\n    end\n    return result\nend, 1)\n\n---@generic K, V\n---@param tbl table<K, V>\n---@return table<V, K>\n_.invert = fun.curryN(function(tbl)\n    local result = {}\n    for k, v in pairs(tbl) do\n        result[v] = k\n    end\n    return result\nend, 1)\n\n---@generic K, V\n---@param transforms table<K, fun (value: V): V>\n---@param tbl table<K, V>\n---@return table<K, V>\n_.evolve = fun.curryN(function(transforms, tbl)\n    local result = shallow_clone(tbl)\n    for key, value in pairs(tbl) do\n        if transforms[key] then\n            result[key] = transforms[key](value)\n        end\n    end\n    return result\nend, 2)\n\n---@generic T : table\n---@param left T\n---@param right T\n---@return T\n_.merge_left = fun.curryN(function(left, right)\n    return vim.tbl_extend(\"force\", right, left)\nend, 2)\n\n---@generic K, V\n---@param key K\n---@param value V\n---@param tbl table<K, V>\n---@return table<K, V>\n_.assoc = fun.curryN(function(key, value, tbl)\n    local res = shallow_clone(tbl)\n    res[key] = value\n    return res\nend, 3)\n\n---@generic K, V\n---@param key K\n---@param tbl table<K, V>\n---@return table<K, V>\n_.dissoc = fun.curryN(function(key, tbl)\n    local res = shallow_clone(tbl)\n    res[key] = nil\n    return res\nend, 2)\n\nreturn _\n"
  },
  {
    "path": "lua/mason-core/functional/type.lua",
    "content": "local fun = require \"mason-core.functional.function\"\nlocal rel = require \"mason-core.functional.relation\"\n\nlocal _ = {}\n\n_.is_nil = rel.equals(nil)\n\n---@param typ type\n---@param value any\n_.is = fun.curryN(function(typ, value)\n    return type(value) == typ\nend, 2)\n\n_.is_list = vim.islist or vim.tbl_islist\n\nreturn _\n"
  },
  {
    "path": "lua/mason-core/installer/InstallHandle.lua",
    "content": "local EventEmitter = require \"mason-core.EventEmitter\"\nlocal Optional = require \"mason-core.optional\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal log = require \"mason-core.log\"\nlocal platform = require \"mason-core.platform\"\nlocal process = require \"mason-core.process\"\nlocal spawn = require \"mason-core.spawn\"\n\nlocal uv = vim.loop\n\n---@alias InstallHandleState\n--- | '\"IDLE\"'\n--- | '\"QUEUED\"'\n--- | '\"ACTIVE\"'\n--- | '\"CLOSED\"'\n\n---@class InstallHandleSpawnHandle\n---@field uv_handle luv_handle\n---@field pid integer\n---@field cmd string\n---@field args string[]\nlocal InstallHandleSpawnHandle = {}\nInstallHandleSpawnHandle.__index = InstallHandleSpawnHandle\n\n---@param luv_handle luv_handle\n---@param pid integer\n---@param cmd string\n---@param args string[]\nfunction InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args)\n    ---@type InstallHandleSpawnHandle\n    local instance = {}\n    setmetatable(instance, InstallHandleSpawnHandle)\n    instance.uv_handle = luv_handle\n    instance.pid = pid\n    instance.cmd = cmd\n    instance.args = args\n    return instance\nend\n\nfunction InstallHandleSpawnHandle:__tostring()\n    return (\"%s %s\"):format(self.cmd, table.concat(self.args, \" \"))\nend\n\n---@class InstallHandle : EventEmitter\n---@field public package AbstractPackage\n---@field state InstallHandleState\n---@field stdio_sink BufferedSink\n---@field is_terminated boolean\n---@field location InstallLocation\n---@field private spawn_handles InstallHandleSpawnHandle[]\nlocal InstallHandle = {}\nInstallHandle.__index = InstallHandle\nsetmetatable(InstallHandle, { __index = EventEmitter })\n\n---@param pkg AbstractPackage\n---@param location InstallLocation\nfunction InstallHandle:new(pkg, location)\n    ---@type InstallHandle\n    local instance = EventEmitter.new(self)\n    local sink = process.BufferedSink:new()\n    sink:connect_events(instance)\n    instance.state = \"IDLE\"\n    instance.package = pkg\n    instance.spawn_handles = {}\n    instance.stdio_sink = sink\n    instance.is_terminated = false\n    instance.location = location\n    return instance\nend\n\n---@param luv_handle luv_handle\n---@param pid integer\n---@param cmd string\n---@param args string[]\nfunction InstallHandle:register_spawn_handle(luv_handle, pid, cmd, args)\n    local spawn_handles = InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args)\n    log.fmt_trace(\"Pushing spawn_handles stack for %s: %s (pid: %s)\", self, spawn_handles, pid)\n    self.spawn_handles[#self.spawn_handles + 1] = spawn_handles\n    self:emit \"spawn_handles:change\"\nend\n\n---@param luv_handle luv_handle\nfunction InstallHandle:deregister_spawn_handle(luv_handle)\n    for i = #self.spawn_handles, 1, -1 do\n        if self.spawn_handles[i].uv_handle == luv_handle then\n            log.fmt_trace(\"Popping spawn_handles stack for %s: %s\", self, self.spawn_handles[i])\n            table.remove(self.spawn_handles, i)\n            self:emit \"spawn_handles:change\"\n            return true\n        end\n    end\n    return false\nend\n\n---@return Optional # Optional<InstallHandleSpawnHandle>\nfunction InstallHandle:peek_spawn_handle()\n    return Optional.of_nilable(self.spawn_handles[#self.spawn_handles])\nend\n\nfunction InstallHandle:is_idle()\n    return self.state == \"IDLE\"\nend\n\nfunction InstallHandle:is_queued()\n    return self.state == \"QUEUED\"\nend\n\nfunction InstallHandle:is_active()\n    return self.state == \"ACTIVE\"\nend\n\nfunction InstallHandle:is_closed()\n    return self.state == \"CLOSED\"\nend\n\nfunction InstallHandle:is_closing()\n    return self:is_closed() or self.is_terminated\nend\n\n---@param new_state InstallHandleState\nfunction InstallHandle:set_state(new_state)\n    local old_state = self.state\n    self.state = new_state\n    log.fmt_trace(\"Changing %s state from %s to %s\", self, old_state, new_state)\n    self:emit(\"state:change\", new_state, old_state)\nend\n\n---@param signal integer\nfunction InstallHandle:kill(signal)\n    assert(not self:is_closed(), \"Cannot kill closed handle.\")\n    log.fmt_trace(\"Sending signal %s to luv handles in %s\", signal, self)\n    for _, spawn_handles in pairs(self.spawn_handles) do\n        process.kill(spawn_handles.uv_handle, signal)\n    end\n    self:emit(\"kill\", signal)\nend\n\n---@param pid integer\nlocal win_taskkill = a.scope(function(pid)\n    spawn.taskkill {\n        \"/f\",\n        \"/t\",\n        \"/pid\",\n        pid,\n    }\nend)\n\nfunction InstallHandle:terminate()\n    assert(not self:is_closed(), \"Cannot terminate closed handle.\")\n    if self.is_terminated then\n        log.fmt_trace(\"Handle is already terminated %s\", self)\n        return\n    end\n    log.fmt_trace(\"Terminating %s\", self)\n    -- https://github.com/libuv/libuv/issues/1133\n    if platform.is.win then\n        for _, spawn_handles in ipairs(self.spawn_handles) do\n            win_taskkill(spawn_handles.pid)\n        end\n    else\n        self:kill(15) -- SIGTERM\n    end\n    self.is_terminated = true\n    self:emit \"terminate\"\n    local check = uv.new_check()\n    check:start(function()\n        for _, spawn_handles in ipairs(self.spawn_handles) do\n            local luv_handle = spawn_handles.uv_handle\n            local ok, is_closing = pcall(luv_handle.is_closing, luv_handle)\n            if ok and not is_closing then\n                return\n            end\n        end\n        check:stop()\n        check:close()\n        if not self:is_closed() then\n            self:close()\n        end\n    end)\nend\n\nfunction InstallHandle:queued()\n    assert(self:is_idle(), \"Can only queue idle handles.\")\n    self:set_state \"QUEUED\"\nend\n\nfunction InstallHandle:active()\n    assert(self:is_idle() or self:is_queued(), \"Can only activate idle or queued handles.\")\n    self:set_state \"ACTIVE\"\nend\n\nfunction InstallHandle:close()\n    log.fmt_trace(\"Closing %s\", self)\n    assert(not self:is_closed(), \"Handle is already closed.\")\n    for _, spawn_handles in ipairs(self.spawn_handles) do\n        local luv_handle = spawn_handles.uv_handle\n        local ok, is_closing = pcall(luv_handle.is_closing, luv_handle)\n        if ok then\n            assert(is_closing, \"There are open libuv handles.\")\n        end\n    end\n    self.spawn_handles = {}\n    self:set_state \"CLOSED\"\n    self:emit \"closed\"\n    self:__clear_event_handlers()\nend\n\nfunction InstallHandle:__tostring()\n    return (\"InstallHandle(package=%s, state=%s)\"):format(self.package, self.state)\nend\n\nreturn InstallHandle\n"
  },
  {
    "path": "lua/mason-core/installer/InstallLocation.lua",
    "content": "local Path = require \"mason-core.path\"\nlocal platform = require \"mason-core.platform\"\nlocal settings = require \"mason.settings\"\n\n---@class InstallLocation\n---@field private dir string\nlocal InstallLocation = {}\nInstallLocation.__index = InstallLocation\n\n---@param dir string\nfunction InstallLocation:new(dir)\n    ---@type InstallLocation\n    local instance = {}\n    setmetatable(instance, self)\n    instance.dir = dir\n    return instance\nend\n\nfunction InstallLocation.global()\n    return InstallLocation:new(settings.current.install_root_dir)\nend\n\nfunction InstallLocation:get_dir()\n    return self.dir\nend\n\nfunction InstallLocation:initialize()\n    local Result = require \"mason-core.result\"\n    local fs = require \"mason-core.fs\"\n\n    return Result.try(function(try)\n        for _, p in ipairs {\n            self.dir,\n            self:bin(),\n            self:share(),\n            self:package(),\n            self:staging(),\n        } do\n            if not fs.sync.dir_exists(p) then\n                try(Result.pcall(fs.sync.mkdirp, p))\n            end\n        end\n    end)\nend\n\n---@param path string?\nfunction InstallLocation:bin(path)\n    return Path.concat { self.dir, \"bin\", path }\nend\n\n---@param path string?\nfunction InstallLocation:share(path)\n    return Path.concat { self.dir, \"share\", path }\nend\n\n---@param path string?\nfunction InstallLocation:opt(path)\n    return Path.concat { self.dir, \"opt\", path }\nend\n\n---@param pkg string?\nfunction InstallLocation:package(pkg)\n    return Path.concat { self.dir, \"packages\", pkg }\nend\n\n---@param path string?\nfunction InstallLocation:staging(path)\n    return Path.concat { self.dir, \"staging\", path }\nend\n\n---@param name string\nfunction InstallLocation:lockfile(name)\n    return self:staging((\"%s.lock\"):format(name))\nend\n\n---@param path string\nfunction InstallLocation:registry(path)\n    return Path.concat { self.dir, \"registries\", path }\nend\n\n---@param pkg string\nfunction InstallLocation:receipt(pkg)\n    return Path.concat { self:package(pkg), \"mason-receipt.json\" }\nend\n\n---@param opts { PATH: '\"append\"' | '\"prepend\"' | '\"skip\"' }\nfunction InstallLocation:set_env(opts)\n    vim.env.MASON = self.dir\n\n    if opts.PATH == \"prepend\" then\n        vim.env.PATH = self:bin() .. platform.path_sep .. vim.env.PATH\n    elseif opts.PATH == \"append\" then\n        vim.env.PATH = vim.env.PATH .. platform.path_sep .. self:bin()\n    end\nend\n\nreturn InstallLocation\n"
  },
  {
    "path": "lua/mason-core/installer/InstallRunner.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal compiler = require \"mason-core.installer.compiler\"\nlocal control = require \"mason-core.async.control\"\nlocal fs = require \"mason-core.fs\"\nlocal linker = require \"mason-core.installer.linker\"\nlocal log = require \"mason-core.log\"\nlocal registry = require \"mason-registry\"\n\nlocal OneShotChannel = control.OneShotChannel\n\nlocal InstallContext = require \"mason-core.installer.context\"\n\n---@class InstallRunner\n---@field handle InstallHandle\n---@field global_semaphore Semaphore\n---@field global_permit Permit?\n---@field package_permit Permit?\nlocal InstallRunner = {}\nInstallRunner.__index = InstallRunner\n\n---@param handle InstallHandle\n---@param semaphore Semaphore\nfunction InstallRunner:new(handle, semaphore)\n    ---@type InstallRunner\n    local instance = {}\n    setmetatable(instance, self)\n    instance.global_semaphore = semaphore\n    instance.handle = handle\n    return instance\nend\n\n---@alias InstallRunnerCallback fun(success: true, receipt: InstallReceipt) | fun(success: false, error: any)\n\n---@param opts PackageInstallOpts\n---@param callback? InstallRunnerCallback\nfunction InstallRunner:execute(opts, callback)\n    local handle = self.handle\n    log.fmt_info(\"Executing installer for %s %s\", handle.package, opts)\n\n    local context = InstallContext:new(handle, opts)\n\n    local tailed_output = {}\n\n    if opts.debug then\n        local function append_log(chunk)\n            tailed_output[#tailed_output + 1] = chunk\n        end\n        handle:on(\"stdout\", append_log)\n        handle:on(\"stderr\", append_log)\n    end\n\n    ---@async\n    local function finalize_logs(success, result)\n        if not success then\n            context.stdio_sink:stderr(tostring(result))\n            context.stdio_sink:stderr \"\\n\"\n        end\n\n        if opts.debug then\n            context.fs:write_file(\"mason-debug.log\", table.concat(tailed_output, \"\"))\n            context.stdio_sink:stdout((\"[debug] Installation directory retained at %q.\\n\"):format(context.cwd:get()))\n        end\n    end\n\n    ---@async\n    local finalize = a.scope(function(success, result)\n        finalize_logs(success, result)\n\n        if not opts.debug and not success then\n            -- clean up installation dir\n            pcall(function()\n                fs.async.rmrf(context.cwd:get())\n            end)\n        end\n\n        if not handle:is_closing() then\n            handle:close()\n        end\n\n        self:release_lock()\n        self:release_permit()\n\n        if success then\n            log.fmt_info(\"Installation succeeded for %s\", handle.package)\n            if callback then\n                callback(true, result.receipt)\n            end\n            handle.package:emit(\"install:success\", result.receipt)\n            registry:emit(\"package:install:success\", handle.package, result.receipt)\n        else\n            log.fmt_error(\"Installation failed for %s error=%s\", handle.package, result)\n            if callback then\n                callback(false, result)\n            end\n            handle.package:emit(\"install:failed\", result)\n            registry:emit(\"package:install:failed\", handle.package, result)\n        end\n    end)\n\n    local cancel_execution = a.run(function()\n        return Result.try(function(try)\n            try(self.handle.location:initialize())\n            try(self:acquire_permit()):receive()\n            try(self:acquire_lock(opts.force))\n\n            context.receipt:with_start_time(vim.loop.gettimeofday())\n            context.receipt:with_registry(context.package.registry:serialize())\n\n            -- 1. initialize working directory\n            try(context.cwd:initialize())\n\n            -- 2. run installer\n            ---@type async fun(ctx: InstallContext): Result\n            local installer = try(compiler.compile_installer(handle.package.spec, opts))\n            try(context:execute(installer))\n\n            -- 3. promote temporary installation dir\n            try(Result.pcall(function()\n                context:promote_cwd()\n            end))\n\n            -- 4. link package & write receipt\n            try(linker.link(context):on_failure(function()\n                -- unlink any links that were made before failure\n                context:build_receipt():on_success(\n                    ---@param receipt InstallReceipt\n                    function(receipt)\n                        linker.unlink(handle.package, receipt, self.handle.location):on_failure(function(err)\n                            log.error(\"Failed to unlink failed installation.\", err)\n                        end)\n                    end\n                )\n            end))\n            ---@type InstallReceipt\n            local receipt = try(context:build_receipt())\n            try(Result.pcall(fs.sync.write_file, handle.location:receipt(handle.package.name), receipt:to_json()))\n            return {\n                receipt = receipt,\n            }\n        end):get_or_throw()\n    end, finalize)\n\n    handle:once(\"terminate\", function()\n        cancel_execution()\n        local function on_close()\n            finalize(false, \"Installation was aborted.\")\n        end\n        if handle:is_closed() then\n            on_close()\n        else\n            handle:once(\"closed\", on_close)\n        end\n    end)\nend\n\n---@async\n---@private\nfunction InstallRunner:release_lock()\n    pcall(fs.async.unlink, self.handle.location:lockfile(self.handle.package.name))\nend\n\n---@async\n---@param force boolean?\n---@private\nfunction InstallRunner:acquire_lock(force)\n    local pkg = self.handle.package\n    log.debug(\"Attempting to lock package\", pkg)\n    local lockfile = self.handle.location:lockfile(pkg.name)\n    if force ~= true and fs.async.file_exists(lockfile) then\n        log.error(\"Lockfile already exists.\", pkg)\n        return Result.failure(\n            (\"Lockfile exists, installation is already running in another process (pid: %s). Run with :MasonInstall --force to bypass.\"):format(\n                fs.async.read_file(lockfile)\n            )\n        )\n    end\n    a.scheduler()\n    fs.async.write_file(lockfile, vim.fn.getpid())\n    log.debug(\"Wrote lockfile\", pkg)\n    return Result.success(lockfile)\nend\n\n---@private\nfunction InstallRunner:acquire_permit()\n    local channel = OneShotChannel:new()\n    log.fmt_debug(\"Acquiring permit for %s\", self.handle.package)\n    local handle = self.handle\n    if handle:is_active() or handle:is_closing() then\n        log.fmt_debug(\"Received active or closing handle %s\", handle)\n        return Result.failure \"Invalid handle state.\"\n    end\n\n    handle:queued()\n    a.run(function()\n        self.global_permit = self.global_semaphore:acquire()\n        self.package_permit = handle.package:acquire_permit()\n    end, function(success, err)\n        if not success or handle:is_closing() then\n            if not success then\n                log.error(\"Acquiring permits failed\", err)\n            end\n            self:release_permit()\n        else\n            log.fmt_debug(\"Activating handle %s\", handle)\n            handle:active()\n            channel:send()\n        end\n    end)\n\n    return Result.success(channel)\nend\n\n---@private\nfunction InstallRunner:release_permit()\n    if self.global_permit then\n        self.global_permit:forget()\n        self.global_permit = nil\n    end\n    if self.package_permit then\n        self.package_permit:forget()\n        self.package_permit = nil\n    end\nend\n\nreturn InstallRunner\n"
  },
  {
    "path": "lua/mason-core/installer/UninstallRunner.lua",
    "content": "local InstallContext = require \"mason-core.installer.context\"\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal compiler = require \"mason-core.installer.compiler\"\nlocal control = require \"mason-core.async.control\"\nlocal fs = require \"mason-core.fs\"\nlocal log = require \"mason-core.log\"\nlocal registry = require \"mason-registry\"\n\nlocal OneShotChannel = control.OneShotChannel\n\n---@class UninstallRunner\n---@field handle InstallHandle\n---@field global_semaphore Semaphore\n---@field package_permit Permit?\n---@field global_permit Permit?\nlocal UninstallRunner = {}\nUninstallRunner.__index = UninstallRunner\n\n---@param handle InstallHandle\n---@param global_semaphore Semaphore\n---@return UninstallRunner\nfunction UninstallRunner:new(handle, global_semaphore)\n    local instance = {}\n    setmetatable(instance, self)\n    instance.handle = handle\n    instance.global_semaphore = global_semaphore\n    return instance\nend\n\n---@param opts PackageUninstallOpts\n---@param callback? InstallRunnerCallback\nfunction UninstallRunner:execute(opts, callback)\n    local pkg = self.handle.package\n    local location = self.handle.location\n    log.fmt_info(\"Executing uninstaller for %s %s\", pkg, opts)\n    a.run(function()\n        Result.try(function(try)\n            if not opts.bypass_permit then\n                try(self:acquire_permit()):receive()\n            end\n            ---@type InstallReceipt?\n            local receipt = pkg:get_receipt(location):or_else(nil)\n            if receipt == nil then\n                log.fmt_warn(\"Receipt not found when uninstalling %s\", pkg)\n            else\n                try(pkg:unlink(location))\n            end\n            fs.sync.rmrf(location:package(pkg.name))\n            return receipt\n        end):get_or_throw()\n    end, function(success, result)\n        if not self.handle:is_closing() then\n            self.handle:close()\n        end\n        self:release_permit()\n\n        if success then\n            local receipt = result\n            log.fmt_info(\"Uninstallation succeeded for %s\", pkg)\n            if callback then\n                callback(true, receipt)\n            end\n            pkg:emit(\"uninstall:success\", receipt)\n            registry:emit(\"package:uninstall:success\", pkg, receipt)\n        else\n            log.fmt_error(\"Uninstallation failed for %s error=%s\", pkg, result)\n            if callback then\n                callback(false, result)\n            end\n            pkg:emit(\"uninstall:failed\", result)\n            registry:emit(\"package:uninstall:failed\", pkg, result)\n        end\n    end)\nend\n\n---@private\nfunction UninstallRunner:acquire_permit()\n    local channel = OneShotChannel:new()\n    log.fmt_debug(\"Acquiring permit for %s\", self.handle.package)\n    local handle = self.handle\n    if handle:is_active() or handle:is_closing() then\n        log.fmt_debug(\"Received active or closing handle %s\", handle)\n        return Result.failure \"Invalid handle state.\"\n    end\n\n    handle:queued()\n    a.run(function()\n        self.global_permit = self.global_semaphore:acquire()\n        self.package_permit = handle.package:acquire_permit()\n    end, function(success, err)\n        if not success or handle:is_closing() then\n            if not success then\n                log.error(\"Acquiring permits failed\", err)\n            end\n            self:release_permit()\n        else\n            log.fmt_debug(\"Activating handle %s\", handle)\n            handle:active()\n            channel:send()\n        end\n    end)\n\n    return Result.success(channel)\nend\n\n---@private\nfunction UninstallRunner:release_permit()\n    if self.global_permit then\n        self.global_permit:forget()\n        self.global_permit = nil\n    end\n    if self.package_permit then\n        self.package_permit:forget()\n        self.package_permit = nil\n    end\nend\n\nreturn UninstallRunner\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/cargo.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal providers = require \"mason-core.providers\"\n\nlocal M = {}\n\n---@class CargoSource : RegistryPackageSource\n\n---@param source CargoSource\n---@param purl Purl\nfunction M.parse(source, purl)\n    local repository_url = _.path({ \"qualifiers\", \"repository_url\" }, purl)\n\n    local git\n    if repository_url then\n        git = {\n            url = repository_url,\n            rev = _.path({ \"qualifiers\", \"rev\" }, purl) == \"true\",\n        }\n    end\n\n    ---@type string?\n    local features = _.path({ \"qualifiers\", \"features\" }, purl)\n    local locked = _.path({ \"qualifiers\", \"locked\" }, purl)\n\n    ---@class ParsedCargoSource : ParsedPackageSource\n    local parsed_source = {\n        crate = purl.name,\n        version = purl.version,\n        features = features,\n        locked = locked ~= \"false\",\n        git = git,\n    }\n    return Result.success(parsed_source)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedCargoSource\nfunction M.install(ctx, source)\n    local cargo = require \"mason-core.installer.managers.cargo\"\n\n    return cargo.install(source.crate, source.version, {\n        git = source.git,\n        features = source.features,\n        locked = source.locked,\n    })\nend\n\n---@async\n---@param purl Purl\nfunction M.get_versions(purl)\n    ---@type string?\n    local repository_url = _.path({ \"qualifiers\", \"repository_url\" }, purl)\n    local rev = _.path({ \"qualifiers\", \"rev\" }, purl)\n    if repository_url then\n        if rev == \"true\" then\n            -- When ?rev=true we're targeting a commit SHA. It's not feasible to retrieve all commit SHAs for a\n            -- repository so we fail instead.\n            return Result.failure \"Unable to retrieve commit SHAs.\"\n        end\n\n        ---@type Result?\n        local git_tags = _.cond {\n            {\n                _.matches \"github.com/(.+)\",\n                _.compose(providers.github.get_all_tags, _.head, _.match \"github.com/(.+)\"),\n            },\n        }(repository_url)\n        if git_tags then\n            return git_tags\n        end\n    end\n    return providers.crates.get_all_versions(purl.name)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/composer.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal providers = require \"mason-core.providers\"\nlocal util = require \"mason-core.installer.compiler.util\"\n\nlocal M = {}\n\n---@param source RegistryPackageSource\n---@param purl Purl\nfunction M.parse(source, purl)\n    ---@class ParsedComposerSource : ParsedPackageSource\n    local parsed_source = {\n        package = (\"%s/%s\"):format(purl.namespace, purl.name),\n        version = purl.version,\n    }\n\n    return Result.success(parsed_source)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedComposerSource\nfunction M.install(ctx, source)\n    local composer = require \"mason-core.installer.managers.composer\"\n    return composer.install(source.package, source.version)\nend\n\n---@async\n---@param purl Purl\nfunction M.get_versions(purl)\n    return providers.packagist.get_all_versions((\"%s/%s\"):format(purl.namespace, purl.name))\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/gem.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal providers = require \"mason-core.providers\"\n\nlocal M = {}\n\n---@class GemSource : RegistryPackageSource\n---@field extra_packages? string[]\n\n---@param source GemSource\n---@param purl Purl\nfunction M.parse(source, purl)\n    ---@class ParsedGemSource : ParsedPackageSource\n    local parsed_source = {\n        package = purl.name,\n        version = purl.version,\n        extra_packages = source.extra_packages,\n    }\n    return Result.success(parsed_source)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedGemSource\nfunction M.install(ctx, source)\n    local gem = require \"mason-core.installer.managers.gem\"\n    return gem.install(source.package, source.version, {\n        extra_packages = source.extra_packages,\n    })\nend\n\n---@async\n---@param purl Purl\nfunction M.get_versions(purl)\n    return providers.rubygems.get_all_versions(purl.name)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/generic/build.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal common = require \"mason-core.installer.managers.common\"\nlocal expr = require \"mason-core.installer.compiler.expr\"\nlocal util = require \"mason-core.installer.compiler.util\"\n\nlocal M = {}\n\n---@class GenericBuildSource : RegistryPackageSource\n---@field build BuildInstruction | BuildInstruction[]\n\n---@param source GenericBuildSource\n---@param purl Purl\n---@param opts PackageInstallOpts\nfunction M.parse(source, purl, opts)\n    return Result.try(function(try)\n        ---@type BuildInstruction\n        local build_instruction = try(util.coalesce_by_target(source.build, opts))\n\n        if build_instruction.env then\n            local expr_ctx = { version = purl.version, target = build_instruction.target }\n            build_instruction.env = try(expr.tbl_interpolate(build_instruction.env, expr_ctx))\n        end\n\n        ---@class ParsedGenericBuildSource : ParsedPackageSource\n        local parsed_source = {\n            build = build_instruction,\n        }\n        return parsed_source\n    end)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedGenericBuildSource\nfunction M.install(ctx, source)\n    return common.run_build_instruction(source.build)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/generic/download.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal common = require \"mason-core.installer.managers.common\"\nlocal expr = require \"mason-core.installer.compiler.expr\"\nlocal util = require \"mason-core.installer.compiler.util\"\n\nlocal M = {}\n\n---@class GenericDownload\n---@field target (Platform | Platform[])?\n---@field files table<string, string>\n\n---@class GenericDownloadSource : RegistryPackageSource\n---@field download GenericDownload | GenericDownload[]\n\n---@param source GenericDownloadSource\n---@param purl Purl\n---@param opts PackageInstallOpts\nfunction M.parse(source, purl, opts)\n    return Result.try(function(try)\n        local download = try(util.coalesce_by_target(source.download, opts))\n\n        local expr_ctx = { version = purl.version }\n        ---@type { files: table<string, string> }\n        local interpolated_download = try(expr.tbl_interpolate(download, expr_ctx))\n\n        ---@type DownloadItem[]\n        local downloads = _.map(function(pair)\n            ---@type DownloadItem\n            return {\n                out_file = pair[1],\n                download_url = pair[2],\n            }\n        end, _.to_pairs(interpolated_download.files))\n\n        ---@class ParsedGenericDownloadSource : ParsedPackageSource\n        local parsed_source = {\n            download = interpolated_download,\n            downloads = downloads,\n        }\n        return parsed_source\n    end)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedGenericDownloadSource\nfunction M.install(ctx, source)\n    return common.download_files(ctx, source.downloads)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/generic/init.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\n\nlocal M = {}\n\n---@param source GenericDownloadSource | GenericBuildSource\n---@param purl Purl\n---@param opts PackageInstallOpts\nfunction M.parse(source, purl, opts)\n    if source.download then\n        source = source --[[@as GenericDownloadSource]]\n        return require(\"mason-core.installer.compiler.compilers.generic.download\").parse(source, purl, opts)\n    elseif source.build then\n        source = source --[[@as GenericBuildSource]]\n        return require(\"mason-core.installer.compiler.compilers.generic.build\").parse(source, purl, opts)\n    else\n        return Result.failure \"Unknown source type.\"\n    end\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedGenericDownloadSource | ParsedGenericBuildSource\nfunction M.install(ctx, source)\n    if source.download then\n        source = source --[[@as ParsedGenericDownloadSource]]\n        return require(\"mason-core.installer.compiler.compilers.generic.download\").install(ctx, source)\n    elseif source.build then\n        source = source --[[@as ParsedGenericBuildSource]]\n        return require(\"mason-core.installer.compiler.compilers.generic.build\").install(ctx, source)\n    else\n        return Result.failure \"Unknown source type.\"\n    end\nend\n\n---@async\n---@param purl Purl\nfunction M.get_versions(purl)\n    return Result.failure \"Unimplemented.\"\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/github/build.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal common = require \"mason-core.installer.managers.common\"\nlocal expr = require \"mason-core.installer.compiler.expr\"\nlocal util = require \"mason-core.installer.compiler.util\"\n\nlocal M = {}\n\n---@class GitHubBuildSource : RegistryPackageSource\n---@field build BuildInstruction | BuildInstruction[]\n\n---@param source GitHubBuildSource\n---@param purl Purl\n---@param opts PackageInstallOpts\nfunction M.parse(source, purl, opts)\n    return Result.try(function(try)\n        ---@type BuildInstruction\n        local build_instruction = try(util.coalesce_by_target(source.build, opts))\n\n        local expr_ctx = { version = purl.version }\n\n        build_instruction.env = try(expr.tbl_interpolate(build_instruction.env or {}, expr_ctx))\n\n        ---@class ParsedGitHubBuildSource : ParsedPackageSource\n        local parsed_source = {\n            build = build_instruction,\n            repo = (\"https://github.com/%s/%s.git\"):format(purl.namespace, purl.name),\n            rev = purl.version,\n        }\n        return parsed_source\n    end)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedGitHubBuildSource\nfunction M.install(ctx, source)\n    local std = require \"mason-core.installer.managers.std\"\n    return Result.try(function(try)\n        try(std.clone(source.repo, { rev = source.rev }))\n        try(common.run_build_instruction(source.build))\n    end)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/github/init.lua",
    "content": "local Result = require \"mason-core.result\"\n\nlocal M = {}\n\n---@param source GitHubReleaseSource | GitHubBuildSource\n---@param purl Purl\n---@param opts PackageInstallOpts\nfunction M.parse(source, purl, opts)\n    if source.asset then\n        source = source --[[@as GitHubReleaseSource]]\n        return require(\"mason-core.installer.compiler.compilers.github.release\").parse(source, purl, opts)\n    elseif source.build then\n        source = source --[[@as GitHubBuildSource]]\n        return require(\"mason-core.installer.compiler.compilers.github.build\").parse(source, purl, opts)\n    else\n        return Result.failure \"Unknown source type.\"\n    end\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedGitHubReleaseSource | ParsedGitHubBuildSource\nfunction M.install(ctx, source)\n    if source.asset then\n        source = source--[[@as ParsedGitHubReleaseSource]]\n        return require(\"mason-core.installer.compiler.compilers.github.release\").install(ctx, source)\n    elseif source.build then\n        source = source--[[@as ParsedGitHubBuildSource]]\n        return require(\"mason-core.installer.compiler.compilers.github.build\").install(ctx, source)\n    else\n        return Result.failure \"Unknown source type.\"\n    end\nend\n\n---@async\n---@param purl Purl\n---@param source GitHubReleaseSource | GitHubBuildSource\nfunction M.get_versions(purl, source)\n    if source.asset then\n        return require(\"mason-core.installer.compiler.compilers.github.release\").get_versions(purl)\n    elseif source.build then\n        -- We can't yet reliably determine the true source (release, tag, commit, etc.) for \"build\" sources.\n        return Result.failure \"Unimplemented.\"\n    else\n        return Result.failure \"Unknown source type.\"\n    end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/github/release.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal common = require \"mason-core.installer.managers.common\"\nlocal expr = require \"mason-core.installer.compiler.expr\"\nlocal providers = require \"mason-core.providers\"\nlocal settings = require \"mason.settings\"\nlocal util = require \"mason-core.installer.compiler.util\"\n\n---@class GitHubReleaseSourceAsset : FileDownloadSpec\n---@field target? Platform | Platform[]\n\n---@class GitHubReleaseSource : RegistryPackageSource\n---@field asset GitHubReleaseSourceAsset | GitHubReleaseSourceAsset[]\n\nlocal M = {}\n\n---@param source GitHubReleaseSource\n---@param purl Purl\n---@param opts PackageInstallOpts\nfunction M.parse(source, purl, opts)\n    return Result.try(function(try)\n        local expr_ctx = { version = purl.version }\n        ---@type GitHubReleaseSourceAsset\n        local asset = try(util.coalesce_by_target(try(expr.tbl_interpolate(source.asset, expr_ctx)), opts))\n\n        local downloads = common.parse_downloads(asset, function(file)\n            return settings.current.github.download_url_template:format(\n                (\"%s/%s\"):format(purl.namespace, purl.name),\n                purl.version,\n                file\n            )\n        end)\n\n        ---@class ParsedGitHubReleaseSource : ParsedPackageSource\n        local parsed_source = {\n            repo = (\"%s/%s\"):format(purl.namespace, purl.name),\n            asset = common.normalize_files(asset),\n            downloads = downloads,\n        }\n        return parsed_source\n    end)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedGitHubReleaseSource\nfunction M.install(ctx, source)\n    return common.download_files(ctx, source.downloads)\nend\n\n---@async\n---@param purl Purl\nfunction M.get_versions(purl)\n    return providers.github.get_all_release_versions((\"%s/%s\"):format(purl.namespace, purl.name))\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/golang.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal providers = require \"mason-core.providers\"\nlocal util = require \"mason-core.installer.compiler.util\"\n\nlocal M = {}\n\n---@param purl Purl\nlocal function get_package_name(purl)\n    if purl.subpath then\n        return (\"%s/%s/%s\"):format(purl.namespace, purl.name, purl.subpath)\n    else\n        return (\"%s/%s\"):format(purl.namespace, purl.name)\n    end\nend\n\n---@class GolangSource : RegistryPackageSource\n---@field extra_packages? string[]\n\n---@param source GolangSource\n---@param purl Purl\nfunction M.parse(source, purl)\n    ---@class ParsedGolangSource : ParsedPackageSource\n    local parsed_source = {\n        package = get_package_name(purl),\n        version = purl.version,\n        extra_packages = source.extra_packages,\n    }\n\n    return Result.success(parsed_source)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedGolangSource\nfunction M.install(ctx, source)\n    local golang = require \"mason-core.installer.managers.golang\"\n\n    return golang.install(source.package, source.version, {\n        extra_packages = source.extra_packages,\n    })\nend\n\n---@async\n---@param purl Purl\nfunction M.get_versions(purl)\n    return providers.golang.get_all_versions((\"%s/%s\"):format(purl.namespace, purl.name))\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/luarocks.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\n\nlocal M = {}\n\n---@param purl Purl\nlocal function parse_package_name(purl)\n    if purl.namespace then\n        return (\"%s/%s\"):format(purl.namespace, purl.name)\n    else\n        return purl.name\n    end\nend\n\nlocal parse_server = _.path { \"qualifiers\", \"repository_url\" }\nlocal parse_dev = _.compose(_.equals \"true\", _.path { \"qualifiers\", \"dev\" })\n\n---@param source RegistryPackageSource\n---@param purl Purl\nfunction M.parse(source, purl)\n    ---@class ParsedLuaRocksSource : ParsedPackageSource\n    local parsed_source = {\n        package = parse_package_name(purl),\n        version = purl.version,\n        ---@type string?\n        server = parse_server(purl),\n        ---@type boolean?\n        dev = parse_dev(purl),\n    }\n\n    return Result.success(parsed_source)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedLuaRocksSource\nfunction M.install(ctx, source)\n    local luarocks = require \"mason-core.installer.managers.luarocks\"\n    return luarocks.install(source.package, source.version, {\n        server = source.server,\n        dev = source.dev,\n    })\nend\n\n---@async\n---@param purl Purl\nfunction M.get_versions(purl)\n    return Result.failure \"Unimplemented.\"\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/mason.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\n\nlocal M = {}\n\n---@param source RegistryPackageSource\n---@param purl Purl\nfunction M.parse(source, purl)\n    if type(source.install) ~= \"function\" and type((getmetatable(source.install) or {}).__call) ~= \"function\" then\n        return Result.failure \"source.install is not a function.\"\n    end\n\n    ---@class ParsedMasonSource : ParsedPackageSource\n    local parsed_source = {\n        purl = purl,\n        ---@type async fun(ctx: InstallContext, purl: Purl)\n        install = source.install,\n    }\n\n    return Result.success(parsed_source)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedMasonSource\nfunction M.install(ctx, source)\n    ctx.spawn.strict_mode = true\n    return Result.pcall(source.install, ctx, source.purl)\n        :on_success(function()\n            ctx.spawn.strict_mode = false\n        end)\n        :on_failure(function()\n            ctx.spawn.strict_mode = false\n        end)\nend\n\n---@async\n---@param purl Purl\nfunction M.get_versions(purl)\n    return Result.failure \"Unimplemented.\"\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/npm.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal providers = require \"mason-core.providers\"\n\n---@param purl Purl\nlocal function purl_to_npm(purl)\n    if purl.namespace then\n        return (\"%s/%s\"):format(purl.namespace, purl.name)\n    else\n        return purl.name\n    end\nend\n\nlocal M = {}\n\n---@class NpmSource : RegistryPackageSource\n---@field extra_packages? string[]\n\n---@param source NpmSource\n---@param purl Purl\nfunction M.parse(source, purl)\n    ---@class ParsedNpmSource : ParsedPackageSource\n    local parsed_source = {\n        package = purl_to_npm(purl),\n        version = purl.version,\n        extra_packages = source.extra_packages,\n    }\n\n    return Result.success(parsed_source)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedNpmSource\nfunction M.install(ctx, source)\n    local npm = require \"mason-core.installer.managers.npm\"\n\n    return Result.try(function(try)\n        try(npm.init())\n        try(npm.install(source.package, source.version, {\n            extra_packages = source.extra_packages,\n        }))\n    end)\nend\n\n---@async\n---@param purl Purl\nfunction M.get_versions(purl)\n    return providers.npm.get_all_versions(purl_to_npm(purl))\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/nuget.lua",
    "content": "local Result = require \"mason-core.result\"\n\nlocal M = {}\n\n---@param source RegistryPackageSource\n---@param purl Purl\nfunction M.parse(source, purl)\n    ---@class ParsedNugetSource : ParsedPackageSource\n    local parsed_source = {\n        package = purl.name,\n        version = purl.version,\n    }\n\n    return Result.success(parsed_source)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedNugetSource\nfunction M.install(ctx, source)\n    local nuget = require \"mason-core.installer.managers.nuget\"\n    return nuget.install(source.package, source.version)\nend\n\n---@async\n---@param purl Purl\nfunction M.get_versions(purl)\n    return Result.failure \"Unimplemented.\"\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/opam.lua",
    "content": "local Result = require \"mason-core.result\"\n\nlocal M = {}\n\n---@param source RegistryPackageSource\n---@param purl Purl\nfunction M.parse(source, purl)\n    ---@class ParsedOpamSource : ParsedPackageSource\n    local parsed_source = {\n        package = purl.name,\n        version = purl.version,\n    }\n\n    return Result.success(parsed_source)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedOpamSource\nfunction M.install(ctx, source)\n    local opam = require \"mason-core.installer.managers.opam\"\n    return opam.install(source.package, source.version)\nend\n\n---@async\n---@param purl Purl\nfunction M.get_versions(purl)\n    return Result.failure \"Unimplemented.\"\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/openvsx.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal common = require \"mason-core.installer.managers.common\"\nlocal expr = require \"mason-core.installer.compiler.expr\"\nlocal providers = require \"mason-core.providers\"\nlocal util = require \"mason-core.installer.compiler.util\"\n\nlocal M = {}\n\n---@class OpenVSXSourceDownload : FileDownloadSpec\n---@field target? Platform | Platform[]\n---@field target_platform? string\n\n---@class OpenVSXSource : RegistryPackageSource\n---@field download OpenVSXSourceDownload | OpenVSXSourceDownload[]\n\n---@param source OpenVSXSource\n---@param purl Purl\n---@param opts PackageInstallOpts\nfunction M.parse(source, purl, opts)\n    return Result.try(function(try)\n        local expr_ctx = { version = purl.version }\n        ---@type OpenVSXSourceDownload\n        local download = try(util.coalesce_by_target(try(expr.tbl_interpolate(source.download, expr_ctx)), opts))\n\n        local downloads = common.parse_downloads(download, function(file)\n            if download.target_platform then\n                return (\"https://open-vsx.org/api/%s/%s/%s/%s/file/%s\"):format(\n                    purl.namespace,\n                    purl.name,\n                    download.target_platform,\n                    purl.version,\n                    file\n                )\n            else\n                return (\"https://open-vsx.org/api/%s/%s/%s/file/%s\"):format(\n                    purl.namespace,\n                    purl.name,\n                    purl.version,\n                    file\n                )\n            end\n        end)\n\n        ---@class ParsedOpenVSXSource : ParsedPackageSource\n        local parsed_source = {\n            download = common.normalize_files(download),\n            downloads = downloads,\n        }\n        return parsed_source\n    end)\nend\n\n---@param ctx InstallContext\n---@param source ParsedOpenVSXSource\nfunction M.install(ctx, source)\n    return common.download_files(ctx, source.downloads)\nend\n\n---@param purl Purl\nfunction M.get_versions(purl)\n    return providers.openvsx.get_all_versions(purl.namespace, purl.name)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/compilers/pypi.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal providers = require \"mason-core.providers\"\nlocal settings = require \"mason.settings\"\n\nlocal M = {}\n\n---@class PypiSource : RegistryPackageSource\n---@field extra_packages? string[]\n\n---@param source PypiSource\n---@param purl Purl\nfunction M.parse(source, purl)\n    ---@class ParsedPypiSource : ParsedPackageSource\n    local parsed_source = {\n        package = purl.name,\n        version = purl.version --[[ @as string ]],\n        extra = _.path({ \"qualifiers\", \"extra\" }, purl),\n        extra_packages = source.extra_packages,\n        pip = {\n            upgrade = settings.current.pip.upgrade_pip,\n            extra_args = settings.current.pip.install_args,\n        },\n    }\n\n    return Result.success(parsed_source)\nend\n\n---@async\n---@param ctx InstallContext\n---@param source ParsedPypiSource\nfunction M.install(ctx, source)\n    local pypi = require \"mason-core.installer.managers.pypi\"\n\n    return Result.try(function(try)\n        try(pypi.init {\n            package = {\n                name = source.package,\n                version = source.version,\n            },\n            upgrade_pip = source.pip.upgrade,\n            install_extra_args = source.pip.extra_args,\n        })\n        try(pypi.install(source.package, source.version, {\n            extra = source.extra,\n            extra_packages = source.extra_packages,\n            install_extra_args = source.pip.extra_args,\n        }))\n    end)\nend\n\n---@async\n---@param purl Purl\nfunction M.get_versions(purl)\n    return providers.pypi.get_all_versions(purl.name)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/expr.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal platform = require \"mason-core.platform\"\n\nlocal M = {}\n\nlocal parse_expr = _.compose(\n    _.apply_spec {\n        value_expr = _.head,\n        filters = _.drop(1),\n    },\n    _.filter(_.complement(_.equals \"\")),\n    _.map(_.trim),\n    _.split \"|\"\n)\n\n---@param predicate (fun(value: string): boolean) | boolean\n---@param value string\nlocal take_if = _.curryN(function(predicate, value)\n    if type(predicate) == \"boolean\" then\n        predicate = _.always(predicate)\n    end\n    return predicate(value) and value or nil\nend, 2)\n\n---@param predicate (fun(value: string): boolean) | boolean\n---@param value string\nlocal take_if_not = _.curryN(function(predicate, value)\n    if type(predicate) == \"boolean\" then\n        predicate = _.always(predicate)\n    end\n    return (not predicate(value)) and value or nil\nend, 2)\n\nlocal FILTERS = {\n    equals = _.equals,\n    not_equals = _.not_equals,\n    strip_prefix = _.strip_prefix,\n    strip_suffix = _.strip_suffix,\n    take_if = take_if,\n    take_if_not = take_if_not,\n    to_lower = _.to_lower,\n    to_upper = _.to_upper,\n    is_platform = function(target)\n        return platform.is[target]\n    end,\n}\n\n---@generic T : table\n---@param tbl T\n---@return T\nlocal function shallow_clone(tbl)\n    local res = {}\n    for k, v in pairs(tbl) do\n        res[k] = v\n    end\n    return res\nend\n\n---@param expr string\n---@param ctx table<string, any>\nlocal function eval(expr, ctx)\n    return setfenv(assert(loadstring(\"return \" .. expr), (\"Failed to parse expression: %q\"):format(expr)), ctx)()\nend\n\n---@param str string\n---@param ctx table<string, any>\nfunction M.interpolate(str, ctx)\n    ctx = shallow_clone(ctx)\n    setmetatable(ctx, { __index = FILTERS })\n    return Result.pcall(function()\n        return _.gsub(\"{{([^}]+)}}\", function(expr)\n            local components = parse_expr(expr)\n\n            local value = eval(components.value_expr, ctx)\n\n            local filters = _.map(function(filter_expr)\n                local filter = eval(filter_expr, ctx)\n                assert(type(filter) == \"function\", (\"Invalid filter expression: %q\"):format(filter_expr))\n                return filter\n            end, components.filters)\n\n            local reduced_value = _.reduce(_.apply_to, value, filters)\n\n            return reduced_value ~= nil and tostring(reduced_value) or \"\"\n        end, str)\n    end)\nend\n\n---@generic T : table\n---@param tbl T\n---@param ctx table\n---@return Result # Result<T>\nfunction M.tbl_interpolate(tbl, ctx)\n    return Result.try(function(try)\n        local interpolated = {}\n        for k, v in pairs(tbl) do\n            if type(k) == \"string\" then\n                k = try(M.interpolate(k, ctx))\n            end\n            if type(v) == \"string\" then\n                interpolated[k] = try(M.interpolate(v, ctx))\n            elseif type(v) == \"table\" then\n                interpolated[k] = try(M.tbl_interpolate(v, ctx))\n            else\n                interpolated[k] = v\n            end\n        end\n        return interpolated\n    end)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/init.lua",
    "content": "local Optional = require \"mason-core.optional\"\nlocal Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal link = require \"mason-core.installer.compiler.link\"\nlocal log = require \"mason-core.log\"\nlocal schemas = require \"mason-core.installer.compiler.schemas\"\nlocal util = require \"mason-core.installer.compiler.util\"\n\nlocal M = {}\n\n---@type table<RegistryPackageSpecSchema, boolean>\nM.SCHEMA_CAP = _.set_of {\n    \"registry+v1\",\n}\n\n---@type table<string, InstallerCompiler>\nlocal COMPILERS = {}\n\n---@param id string\n---@param compiler InstallerCompiler\nfunction M.register_compiler(id, compiler)\n    COMPILERS[id] = compiler\nend\n\nM.register_compiler(\"cargo\", _.lazy_require \"mason-core.installer.compiler.compilers.cargo\")\nM.register_compiler(\"composer\", _.lazy_require \"mason-core.installer.compiler.compilers.composer\")\nM.register_compiler(\"gem\", _.lazy_require \"mason-core.installer.compiler.compilers.gem\")\nM.register_compiler(\"generic\", _.lazy_require \"mason-core.installer.compiler.compilers.generic\")\nM.register_compiler(\"github\", _.lazy_require \"mason-core.installer.compiler.compilers.github\")\nM.register_compiler(\"golang\", _.lazy_require \"mason-core.installer.compiler.compilers.golang\")\nM.register_compiler(\"luarocks\", _.lazy_require \"mason-core.installer.compiler.compilers.luarocks\")\nM.register_compiler(\"mason\", _.lazy_require \"mason-core.installer.compiler.compilers.mason\")\nM.register_compiler(\"npm\", _.lazy_require \"mason-core.installer.compiler.compilers.npm\")\nM.register_compiler(\"nuget\", _.lazy_require \"mason-core.installer.compiler.compilers.nuget\")\nM.register_compiler(\"opam\", _.lazy_require \"mason-core.installer.compiler.compilers.opam\")\nM.register_compiler(\"openvsx\", _.lazy_require \"mason-core.installer.compiler.compilers.openvsx\")\nM.register_compiler(\"pypi\", _.lazy_require \"mason-core.installer.compiler.compilers.pypi\")\n\n---@param purl Purl\n---@return Result # Result<InstallerCompiler>\nfunction M.get_compiler(purl)\n    return Optional.of_nilable(COMPILERS[purl.type])\n        :ok_or((\"Current version of mason.nvim is not capable of parsing package type %q.\"):format(purl.type))\nend\n\n---@class InstallerCompiler\n---@field parse fun(source: RegistryPackageSource, purl: Purl, opts: PackageInstallOpts): Result\n---@field install async fun(ctx: InstallContext, source: ParsedPackageSource, purl: Purl): Result\n---@field get_versions async fun(purl: Purl, source: RegistryPackageSource): Result # Result<string[]>\n\n---@class ParsedPackageSource\n\n---Upserts {dst} with contents of {src}. List table values will be merged, with contents of {src} prepended.\n---@param dst table\n---@param src table\nlocal function upsert(dst, src)\n    for k, v in pairs(src) do\n        if type(v) == \"table\" then\n            if _.is_list(v) then\n                dst[k] = _.concat(v, dst[k] or {})\n            else\n                dst[k] = upsert(dst[k] or {}, src[k])\n            end\n        else\n            dst[k] = v\n        end\n    end\n    return dst\nend\n\n---@param source RegistryPackageSource\n---@param version string?\n---@return RegistryPackageSource\nlocal function coalesce_source(source, version)\n    if version and source.version_overrides then\n        for i = #source.version_overrides, 1, -1 do\n            local version_override = source.version_overrides[i]\n            local version_type, constraint = unpack(_.split(\":\", version_override.constraint))\n            if version_type == \"semver\" then\n                local semver = require \"mason-core.semver\"\n                local version_match = Result.try(function(try)\n                    local requested_version = try(semver.parse(version))\n                    if _.starts_with(\"<=\", constraint) then\n                        local rule_version = try(semver.parse(_.strip_prefix(\"<=\", constraint)))\n                        return requested_version <= rule_version\n                    elseif _.starts_with(\">=\", constraint) then\n                        local rule_version = try(semver.parse(_.strip_prefix(\">=\", constraint)))\n                        return requested_version >= rule_version\n                    else\n                        local rule_version = try(semver.parse(constraint))\n                        return requested_version == rule_version\n                    end\n                end):get_or_else(false)\n\n                if version_match then\n                    return _.dissoc(\"constraint\", version_override)\n                end\n            end\n        end\n    end\n    return _.dissoc(\"version_overrides\", source)\nend\n\n---@param spec RegistryPackageSpec\n---@param opts PackageInstallOpts\nfunction M.parse(spec, opts)\n    log.trace(\"Parsing spec\", spec.name, opts)\n    return Result.try(function(try)\n        if not M.SCHEMA_CAP[spec.schema] then\n            return Result.failure(\n                (\"Current version of mason.nvim is not capable of parsing package schema version %q.\"):format(\n                    spec.schema\n                )\n            )\n        end\n\n        local source = coalesce_source(spec.source, opts.version)\n\n        if source.supported_platforms then\n            try(util.ensure_valid_platform(source.supported_platforms))\n        end\n\n        ---@type Purl\n        local purl = try(Purl.parse(source.id))\n        log.trace(\"Parsed purl.\", source.id, purl)\n        if opts.version then\n            purl.version = opts.version\n        end\n\n        ---@type InstallerCompiler\n        local compiler = try(M.get_compiler(purl))\n        log.trace(\"Found compiler for purl.\", source.id)\n        local parsed_source = try(compiler.parse(source, purl, opts))\n        log.trace(\"Parsed source for purl.\", source.id, parsed_source)\n        return {\n            compiler = compiler,\n            source = vim.tbl_extend(\"keep\", parsed_source, source),\n            raw_source = source,\n            purl = purl,\n        }\n    end):on_failure(function(err)\n        log.debug(\"Failed to parse spec spec\", spec.name, err)\n    end)\nend\n\n---@async\n---@param spec RegistryPackageSpec\n---@param opts PackageInstallOpts\nfunction M.compile_installer(spec, opts)\n    log.debug(\"Compiling installer.\", spec.name, opts)\n    return Result.try(function(try)\n        -- Parsers run synchronously and may access API functions, so we schedule before-hand.\n        a.scheduler()\n\n        local map_parse_err = _.cond {\n            {\n                _.equals \"PLATFORM_UNSUPPORTED\",\n                function()\n                    if opts.target then\n                        return (\"Platform %q is unsupported.\"):format(opts.target)\n                    else\n                        return \"The current platform is unsupported.\"\n                    end\n                end,\n            },\n            { _.T, _.identity },\n        }\n\n        ---@type { purl: Purl, compiler: InstallerCompiler, source: ParsedPackageSource, raw_source: RegistryPackageSource }\n        local parsed = try(M.parse(spec, opts):map_err(map_parse_err))\n\n        ---@async\n        ---@param ctx InstallContext\n        return function(ctx)\n            return Result.try(function(try)\n                if ctx.opts.version then\n                    try(util.ensure_valid_version(function()\n                        return parsed.compiler.get_versions(parsed.purl, parsed.raw_source)\n                    end))\n                end\n\n                -- Run installer\n                a.scheduler()\n                try(parsed.compiler.install(ctx, parsed.source, parsed.purl))\n\n                if spec.schemas then\n                    local result = schemas.download(ctx, spec, parsed.purl, parsed.source):on_failure(function(err)\n                        log.error(\"Failed to download schemas\", ctx.package, err)\n                    end)\n                    if opts.strict then\n                        -- schema download sources are not considered stable nor a critical feature, so we only fail in strict mode\n                        try(result)\n                    end\n                end\n\n                -- Expand & register links\n                if spec.bin then\n                    try(link.bin(ctx, spec, parsed.purl, parsed.source))\n                end\n                if spec.share then\n                    try(link.share(ctx, spec, parsed.purl, parsed.source))\n                end\n                if spec.opt then\n                    try(link.opt(ctx, spec, parsed.purl, parsed.source))\n                end\n\n                ctx.receipt:with_source {\n                    type = ctx.package.spec.schema,\n                    id = Purl.compile(parsed.purl),\n                    -- Exclude the \"install\" field from \"mason\" sources because this is a Lua function.\n                    raw = parsed.purl.type == \"mason\" and _.dissoc(\"install\", parsed.raw_source) or parsed.raw_source,\n                }\n                ctx.receipt:with_install_options(opts)\n            end)\n        end\n    end)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/link.lua",
    "content": "local Optional = require \"mason-core.optional\"\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal expr = require \"mason-core.installer.compiler.expr\"\nlocal fs = require \"mason-core.fs\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal platform = require \"mason-core.platform\"\n\nlocal M = {}\n\nlocal filter_empty_values = _.compose(\n    _.from_pairs,\n    _.filter(function(pair)\n        return pair[2] ~= \"\"\n    end),\n    _.to_pairs\n)\n\nlocal bin_delegates = {\n    [\"luarocks\"] = function(target)\n        return require(\"mason-core.installer.managers.luarocks\").bin_path(target)\n    end,\n    [\"composer\"] = function(target)\n        return require(\"mason-core.installer.managers.composer\").bin_path(target)\n    end,\n    [\"opam\"] = function(target)\n        return require(\"mason-core.installer.managers.opam\").bin_path(target)\n    end,\n    [\"python\"] = function(target, bin)\n        local installer = require \"mason-core.installer\"\n        local ctx = installer.context()\n        if not ctx.fs:file_exists(target) then\n            return Result.failure((\"Cannot write python wrapper for path %q as it doesn't exist.\"):format(target))\n        end\n        return Result.pcall(function()\n            local python = platform.is.win and \"python\" or \"python3\"\n            return ctx:write_shell_exec_wrapper(\n                bin,\n                (\"%s %q\"):format(python, path.concat { ctx:get_install_path(), target })\n            )\n        end)\n    end,\n    [\"php\"] = function(target, bin)\n        local installer = require \"mason-core.installer\"\n        local ctx = installer.context()\n        return Result.pcall(function()\n            return ctx:write_php_exec_wrapper(bin, target)\n        end)\n    end,\n    [\"pyvenv\"] = function(target, bin)\n        local installer = require \"mason-core.installer\"\n        local ctx = installer.context()\n        return Result.pcall(function()\n            return ctx:write_pyvenv_exec_wrapper(bin, target)\n        end)\n    end,\n    [\"dotnet\"] = function(target, bin)\n        local installer = require \"mason-core.installer\"\n        local ctx = installer.context()\n        if not ctx.fs:file_exists(target) then\n            return Result.failure((\"Cannot write dotnet wrapper for path %q as it doesn't exist.\"):format(target))\n        end\n        return Result.pcall(function()\n            return ctx:write_shell_exec_wrapper(\n                bin,\n                (\"dotnet %q\"):format(path.concat {\n                    ctx:get_install_path(),\n                    target,\n                })\n            )\n        end)\n    end,\n    [\"node\"] = function(target, bin)\n        local installer = require \"mason-core.installer\"\n        local ctx = installer.context()\n        return Result.pcall(function()\n            return ctx:write_node_exec_wrapper(bin, target)\n        end)\n    end,\n    [\"ruby\"] = function(target, bin)\n        local installer = require \"mason-core.installer\"\n        local ctx = installer.context()\n        return Result.pcall(function()\n            return ctx:write_ruby_exec_wrapper(bin, target)\n        end)\n    end,\n    [\"exec\"] = function(target, bin)\n        local installer = require \"mason-core.installer\"\n        local ctx = installer.context()\n        return Result.pcall(function()\n            return ctx:write_exec_wrapper(bin, target)\n        end)\n    end,\n    [\"java-jar\"] = function(target, bin)\n        local installer = require \"mason-core.installer\"\n        local ctx = installer.context()\n        if not ctx.fs:file_exists(target) then\n            return Result.failure((\"Cannot write Java JAR wrapper for path %q as it doesn't exist.\"):format(target))\n        end\n        return Result.pcall(function()\n            return ctx:write_shell_exec_wrapper(\n                bin,\n                (\"java -jar %q\"):format(path.concat {\n                    ctx:get_install_path(),\n                    target,\n                })\n            )\n        end)\n    end,\n    [\"nuget\"] = function(target)\n        return require(\"mason-core.installer.managers.nuget\").bin_path(target)\n    end,\n    [\"npm\"] = function(target)\n        return require(\"mason-core.installer.managers.npm\").bin_path(target)\n    end,\n    [\"gem\"] = function(target)\n        return require(\"mason-core.installer.managers.gem\").create_bin_wrapper(target)\n    end,\n    [\"cargo\"] = function(target)\n        return require(\"mason-core.installer.managers.cargo\").bin_path(target)\n    end,\n    [\"pypi\"] = function(target)\n        return require(\"mason-core.installer.managers.pypi\").bin_path(target)\n    end,\n    [\"golang\"] = function(target)\n        return require(\"mason-core.installer.managers.golang\").bin_path(target)\n    end,\n}\n\n---Expands bin specification from spec and registers bins to be linked.\n---@async\n---@param ctx InstallContext\n---@param spec RegistryPackageSpec\n---@param purl Purl\n---@param source ParsedPackageSource\nlocal function expand_bin(ctx, spec, purl, source)\n    log.debug(\"Registering bin links\", ctx.package, spec.bin)\n    return Result.try(function(try)\n        local expr_ctx = {\n            version = purl.version,\n            source = source,\n        }\n\n        local bin_table = spec.bin\n        if not bin_table then\n            log.fmt_debug(\"%s spec provides no bin.\", ctx.package)\n            return\n        end\n\n        local interpolated_bins = filter_empty_values(try(expr.tbl_interpolate(bin_table, expr_ctx)))\n\n        local expanded_bin_table = {}\n        for bin, target in pairs(interpolated_bins) do\n            -- Expand \"npm:typescript-language-server\"-like expressions\n            local delegated_bin = _.match(\"^(.+):(.+)$\", target)\n            if #delegated_bin > 0 then\n                local bin_type, executable = unpack(delegated_bin)\n                log.fmt_trace(\"Transforming managed executable=%s via %s\", executable, bin_type)\n                local delegate =\n                    try(Optional.of_nilable(bin_delegates[bin_type]):ok_or((\"Unknown bin type: %s\"):format(bin_type)))\n                target = try(delegate(executable, bin))\n            end\n\n            log.fmt_debug(\"Expanded bin link %s -> %s\", bin, target)\n            if not ctx.fs:file_exists(target) then\n                return Result.failure((\"Tried to link bin %q to non-existent target %q.\"):format(bin, target))\n            end\n\n            if platform.is.unix then\n                ctx.fs:chmod_exec(target)\n            end\n\n            expanded_bin_table[bin] = target\n        end\n        return expanded_bin_table\n    end)\nend\n\nlocal is_dir_path = _.matches \"/$\"\n\n---Expands symlink path specifications from spec and returns symlink file table.\n---@async\n---@param ctx InstallContext\n---@param purl Purl\n---@param source ParsedPackageSource\n---@param file_spec_table table<string, string>\nlocal function expand_file_spec(ctx, purl, source, file_spec_table)\n    log.debug(\"Registering symlinks\", ctx.package, file_spec_table)\n    return Result.try(function(try)\n        local expr_ctx = { version = purl.version, source = source }\n\n        ---@type table<string, string>\n        local interpolated_paths = filter_empty_values(try(expr.tbl_interpolate(file_spec_table, expr_ctx)))\n\n        ---@type table<string, string>\n        local expanded_links = {}\n\n        for dest, source_path in pairs(interpolated_paths) do\n            local cwd = ctx.cwd:get()\n\n            if is_dir_path(dest) then\n                -- linking dir -> dir\n                if not is_dir_path(source_path) then\n                    return Result.failure((\"Cannot link file %q to dir %q.\"):format(source_path, dest))\n                end\n\n                a.scheduler()\n\n                local glob = path.concat { cwd, source_path } .. \"**/*\"\n                log.fmt_trace(\"Symlink glob for %s: %s\", ctx.package, glob)\n\n                ---@type string[]\n                local files = _.filter_map(function(abs_path)\n                    if not fs.sync.file_exists(abs_path) then\n                        -- only link actual files (e.g. exclude directory entries from glob)\n                        return Optional.empty()\n                    end\n                    -- turn into relative paths\n                    return Optional.of(abs_path:sub(#cwd + 2)) -- + 2 to remove leading path separator (/)\n                end, vim.fn.glob(glob, false, true))\n\n                log.fmt_trace(\"Expanded glob %s: %s\", glob, files)\n\n                for __, file in ipairs(files) do\n                    -- File destination should be relative to the source directory. For example, should the source_path\n                    -- be \"gh_2.22.1_macOS_amd64/share/man/\" and dest be \"man/\", it should link source files to the\n                    -- following destinations:\n                    --\n                    --   gh_2.22.1_macOS_amd64/share/man/                     man/\n                    --   -------------------------------------------------------------------------\n                    --   gh_2.22.1_macOS_amd64/share/man/man1/gh.1            man/man1/gh.1\n                    --   gh_2.22.1_macOS_amd64/share/man/man1/gh-run.1        man/man1/gh-run.1\n                    --   gh_2.22.1_macOS_amd64/share/man/man1/gh-ssh-key.1    man/man1/gh-run.1\n                    --\n                    local file_dest = path.concat {\n                        _.trim_end_matches(\"/\", dest),\n                        file:sub(#source_path + 1),\n                    }\n                    expanded_links[file_dest] = file\n                end\n            else\n                -- linking file -> file\n                if is_dir_path(source_path) then\n                    return Result.failure((\"Cannot link dir %q to file %q.\"):format(source_path, dest))\n                end\n                expanded_links[dest] = source_path\n            end\n        end\n\n        return expanded_links\n    end)\nend\n\n---@async\n---@param ctx InstallContext\n---@param spec RegistryPackageSpec\n---@param purl Purl\n---@param source ParsedPackageSource\n---@nodiscard\nM.bin = function(ctx, spec, purl, source)\n    return expand_bin(ctx, spec, purl, source):on_success(function(links)\n        ctx.links.bin = vim.tbl_extend(\"force\", ctx.links.bin, links)\n    end)\nend\n\n---@async\n---@param ctx InstallContext\n---@param spec RegistryPackageSpec\n---@param purl Purl\n---@param source ParsedPackageSource\n---@nodiscard\nM.share = function(ctx, spec, purl, source)\n    return expand_file_spec(ctx, purl, source, spec.share):on_success(function(links)\n        ctx.links.share = vim.tbl_extend(\"force\", ctx.links.share, links)\n    end)\nend\n\n---@async\n---@param ctx InstallContext\n---@param spec RegistryPackageSpec\n---@param purl Purl\n---@param source ParsedPackageSource\n---@nodiscard\nM.opt = function(ctx, spec, purl, source)\n    return expand_file_spec(ctx, purl, source, spec.opt):on_success(function(links)\n        ctx.links.opt = vim.tbl_extend(\"force\", ctx.links.opt, links)\n    end)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/schemas.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal expr = require \"mason-core.installer.compiler.expr\"\nlocal fetch = require \"mason-core.fetch\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal std = require \"mason-core.installer.managers.std\"\n\nlocal M = {}\n\n---@async\n---@param ctx InstallContext\n---@param url string\nlocal function download_lsp_schema(ctx, url)\n    return Result.try(function(try)\n        local is_vscode_schema = _.starts_with(\"vscode:\", url)\n        local out_file = path.concat { \"mason-schemas\", \"lsp.json\" }\n        local share_file = path.concat { \"mason-schemas\", \"lsp\", (\"%s.json\"):format(ctx.package.name) }\n\n        if is_vscode_schema then\n            local url = unpack(_.match(\"^vscode:(.+)$\", url))\n            ctx.stdio_sink:stdout((\"Downloading LSP configuration schema from %q…\\n\"):format(url))\n            local json = try(fetch(url))\n\n            ---@type { contributes?: { configuration?: table } }\n            local schema = try(Result.pcall(vim.json.decode, json))\n            local configuration = schema.contributes and schema.contributes.configuration\n\n            if configuration then\n                ctx.fs:write_file(out_file, vim.json.encode(configuration) --[[@as string]])\n                ctx.links.share[share_file] = out_file\n            else\n                return Result.failure \"Unable to find LSP entry in VSCode schema.\"\n            end\n        else\n            ctx.stdio_sink:stdout((\"Downloading LSP configuration schema from %q…\\n\"):format(url))\n            try(std.download_file(url, out_file))\n            ctx.links.share[share_file] = out_file\n        end\n    end)\nend\n\n---@async\n---@param ctx InstallContext\n---@param spec RegistryPackageSpec\n---@param purl Purl\n---@param source ParsedPackageSource\n---@nodiscard\nfunction M.download(ctx, spec, purl, source)\n    return Result.try(function(try)\n        log.debug(\"schemas: download\", ctx.package, spec.schemas)\n        local schemas = spec.schemas\n        if not schemas then\n            return\n        end\n        ---@type RegistryPackageSchemas\n        local interpolated_schemas = try(expr.tbl_interpolate(schemas, { version = purl.version, source = source }))\n        ctx.fs:mkdir \"mason-schemas\"\n\n        if interpolated_schemas.lsp then\n            try(a.wait_first {\n                function()\n                    return download_lsp_schema(ctx, interpolated_schemas.lsp)\n                end,\n                function()\n                    a.sleep(5000)\n                    return Result.failure \"Schema download timed out.\"\n                end,\n            })\n        end\n    end)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/compiler/util.lua",
    "content": "local Optional = require \"mason-core.optional\"\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal installer = require \"mason-core.installer\"\nlocal log = require \"mason-core.log\"\nlocal platform = require \"mason-core.platform\"\n\nlocal M = {}\n\n---@generic T : { target: Platform | Platform[] }\n---@param candidates T[] | T\n---@param opts PackageInstallOpts\n---@return Result # Result<T>\nfunction M.coalesce_by_target(candidates, opts)\n    if not _.is_list(candidates) then\n        return Result.success(candidates)\n    end\n    return Optional.of_nilable(_.find_first(function(asset)\n        if opts.target then\n            -- Matching against a provided target rather than the current platform is an escape hatch primarily meant\n            -- for automated testing purposes.\n            if type(asset.target) == \"table\" then\n                return _.any(_.equals(opts.target), asset.target)\n            else\n                return asset.target == opts.target\n            end\n        else\n            if type(asset.target) == \"table\" then\n                return _.any(function(target)\n                    return platform.is[target]\n                end, asset.target)\n            else\n                return platform.is[asset.target]\n            end\n        end\n    end, candidates)):ok_or \"PLATFORM_UNSUPPORTED\"\nend\n\n---Checks whether a custom version of a package installation corresponds to a valid version.\n---@async\n---@param versions_thunk async fun(): Result Result<string[]>\nfunction M.ensure_valid_version(versions_thunk)\n    local ctx = installer.context()\n    local version = ctx.opts.version\n\n    if version and not ctx.opts.force then\n        ctx.stdio_sink:stdout \"Fetching available versions…\\n\"\n        local all_versions = versions_thunk()\n        if all_versions:is_failure() then\n            log.warn(\"Failed to fetch versions for package\", ctx.package)\n            -- Gracefully fail (i.e. optimistically continue package installation)\n            return Result.success()\n        end\n        all_versions = all_versions:get_or_else {}\n\n        if not _.any(_.equals(version), all_versions) then\n            ctx.stdio_sink:stderr((\"Tried to install invalid version %q. Available versions:\\n\"):format(version))\n            ctx.stdio_sink:stderr(_.compose(_.join \"\\n\", _.map(_.join \", \"), _.split_every(15))(all_versions))\n            ctx.stdio_sink:stderr \"\\n\\n\"\n            ctx.stdio_sink:stderr(\n                (\"Run with --force flag to bypass version validation:\\n  :MasonInstall --force %s@%s\\n\\n\"):format(\n                    ctx.package.name,\n                    version\n                )\n            )\n            return Result.failure((\"Version %q is not available.\"):format(version))\n        end\n    end\n\n    return Result.success()\nend\n\n---@param platforms string[]\nfunction M.ensure_valid_platform(platforms)\n    if not _.any(function(target)\n        return platform.is[target]\n    end, platforms) then\n        return Result.failure \"PLATFORM_UNSUPPORTED\"\n    end\n    return Result.success()\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/context/InstallContextCwd.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal fs = require \"mason-core.fs\"\nlocal path = require \"mason-core.path\"\n\n---@class InstallContextCwd\n---@field private handle InstallHandle\n---@field private cwd string?\nlocal InstallContextCwd = {}\nInstallContextCwd.__index = InstallContextCwd\n\n---@param handle InstallHandle\nfunction InstallContextCwd:new(handle)\n    ---@type InstallContextCwd\n    local instance = {}\n    setmetatable(instance, self)\n    instance.handle = handle\n    instance.cwd = nil\n    return instance\nend\n\nfunction InstallContextCwd:initialize()\n    return Result.try(function(try)\n        local staging_dir = self.handle.location:staging(self.handle.package.name)\n        if fs.sync.dir_exists(staging_dir) then\n            try(Result.pcall(fs.sync.rmrf, staging_dir))\n        end\n        try(Result.pcall(fs.sync.mkdirp, staging_dir))\n        self:set(staging_dir)\n    end)\nend\n\nfunction InstallContextCwd:get()\n    assert(self.cwd ~= nil, \"Tried to access cwd before it was set.\")\n    return self.cwd\nend\n\n---@param new_abs_cwd string\nfunction InstallContextCwd:set(new_abs_cwd)\n    assert(type(new_abs_cwd) == \"string\", \"new_cwd is not a string\")\n    assert(\n        path.is_subdirectory(self.handle.location:get_dir(), new_abs_cwd),\n        (\"%q is not a subdirectory of %q\"):format(new_abs_cwd, self.handle.location)\n    )\n    self.cwd = new_abs_cwd\n    return self\nend\n\nreturn InstallContextCwd\n"
  },
  {
    "path": "lua/mason-core/installer/context/InstallContextFs.lua",
    "content": "local fs = require \"mason-core.fs\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\n\n---@class InstallContextFs\n---@field private cwd InstallContextCwd\nlocal InstallContextFs = {}\nInstallContextFs.__index = InstallContextFs\n\n---@param cwd InstallContextCwd\nfunction InstallContextFs:new(cwd)\n    ---@type InstallContextFs\n    local instance = {}\n    setmetatable(instance, InstallContextFs)\n    instance.cwd = cwd\n    return instance\nend\n\n---@async\n---@param rel_path string The relative path from the current working directory to the file to append.\n---@param contents string\nfunction InstallContextFs:append_file(rel_path, contents)\n    return fs.async.append_file(path.concat { self.cwd:get(), rel_path }, contents)\nend\n\n---@async\n---@param rel_path string The relative path from the current working directory to the file to write.\n---@param contents string\nfunction InstallContextFs:write_file(rel_path, contents)\n    return fs.async.write_file(path.concat { self.cwd:get(), rel_path }, contents)\nend\n\n---@async\n---@param rel_path string The relative path from the current working directory to the file to read.\nfunction InstallContextFs:read_file(rel_path)\n    return fs.async.read_file(path.concat { self.cwd:get(), rel_path })\nend\n\n---@async\n---@param rel_path string The relative path from the current working directory.\nfunction InstallContextFs:file_exists(rel_path)\n    return fs.async.file_exists(path.concat { self.cwd:get(), rel_path })\nend\n\n---@async\n---@param rel_path string The relative path from the current working directory.\nfunction InstallContextFs:dir_exists(rel_path)\n    return fs.async.dir_exists(path.concat { self.cwd:get(), rel_path })\nend\n\n---@async\n---@param rel_path string The relative path from the current working directory.\nfunction InstallContextFs:rmrf(rel_path)\n    return fs.async.rmrf(path.concat { self.cwd:get(), rel_path })\nend\n\n---@async\n---@param rel_path string The relative path from the current working directory.\nfunction InstallContextFs:unlink(rel_path)\n    return fs.async.unlink(path.concat { self.cwd:get(), rel_path })\nend\n\n---@async\n---@param old_path string\n---@param new_path string\nfunction InstallContextFs:rename(old_path, new_path)\n    return fs.async.rename(path.concat { self.cwd:get(), old_path }, path.concat { self.cwd:get(), new_path })\nend\n\n---@async\n---@param dir_path string\nfunction InstallContextFs:mkdir(dir_path)\n    return fs.async.mkdir(path.concat { self.cwd:get(), dir_path })\nend\n\n---@async\n---@param dir_path string\nfunction InstallContextFs:mkdirp(dir_path)\n    return fs.async.mkdirp(path.concat { self.cwd:get(), dir_path })\nend\n\n---@async\n---@param file_path string\nfunction InstallContextFs:chmod_exec(file_path)\n    local bit = require \"bit\"\n    -- see chmod(2)\n    local USR_EXEC = 0x40\n    local GRP_EXEC = 0x8\n    local ALL_EXEC = 0x1\n    local EXEC = bit.bor(USR_EXEC, GRP_EXEC, ALL_EXEC)\n    local fstat = self:fstat(file_path)\n    if bit.band(fstat.mode, EXEC) ~= EXEC then\n        local plus_exec = bit.bor(fstat.mode, EXEC)\n        log.fmt_debug(\"Setting exec flags on file %s %o -> %o\", file_path, fstat.mode, plus_exec)\n        self:chmod(file_path, plus_exec) -- chmod +x\n    end\nend\n\n---@async\n---@param file_path string\n---@param mode integer\nfunction InstallContextFs:chmod(file_path, mode)\n    return fs.async.chmod(path.concat { self.cwd:get(), file_path }, mode)\nend\n\n---@async\n---@param file_path string\nfunction InstallContextFs:fstat(file_path)\n    return fs.async.fstat(path.concat { self.cwd:get(), file_path })\nend\n\nreturn InstallContextFs\n"
  },
  {
    "path": "lua/mason-core/installer/context/InstallContextSpawn.lua",
    "content": "local spawn = require \"mason-core.spawn\"\n\n---@class InstallContextSpawn\n---@field strict_mode boolean Whether spawn failures should raise an exception rather then return a Result.\n---@field private cwd InstallContextCwd\n---@field private handle InstallHandle\n---@field [string] async fun(opts: SpawnArgs): Result\nlocal InstallContextSpawn = {}\n\n---@param handle InstallHandle\n---@param cwd InstallContextCwd\n---@param strict_mode boolean\nfunction InstallContextSpawn:new(handle, cwd, strict_mode)\n    ---@type InstallContextSpawn\n    local instance = {}\n    setmetatable(instance, self)\n    instance.cwd = cwd\n    instance.handle = handle\n    instance.strict_mode = strict_mode\n    return instance\nend\n\n---@param cmd string\nfunction InstallContextSpawn:__index(cmd)\n    ---@param args JobSpawnOpts\n    return function(args)\n        args.cwd = args.cwd or self.cwd:get()\n        args.stdio_sink = args.stdio_sink or self.handle.stdio_sink\n        local on_spawn = args.on_spawn\n        local captured_handle\n        args.on_spawn = function(handle, stdio, pid, ...)\n            captured_handle = handle\n            self.handle:register_spawn_handle(handle, pid, cmd, spawn._flatten_cmd_args(args))\n            if on_spawn then\n                on_spawn(handle, stdio, pid, ...)\n            end\n        end\n        local function pop_spawn_stack()\n            if captured_handle then\n                self.handle:deregister_spawn_handle(captured_handle)\n            end\n        end\n        local result = spawn[cmd](args):on_success(pop_spawn_stack):on_failure(pop_spawn_stack)\n        if self.strict_mode then\n            return result:get_or_throw()\n        else\n            return result\n        end\n    end\nend\n\nreturn InstallContextSpawn\n"
  },
  {
    "path": "lua/mason-core/installer/context/init.lua",
    "content": "local InstallContextCwd = require \"mason-core.installer.context.InstallContextCwd\"\nlocal InstallContextFs = require \"mason-core.installer.context.InstallContextFs\"\nlocal InstallContextSpawn = require \"mason-core.installer.context.InstallContextSpawn\"\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal fetch = require \"mason-core.fetch\"\nlocal fs = require \"mason-core.fs\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal platform = require \"mason-core.platform\"\nlocal receipt = require \"mason-core.receipt\"\n\n---@class InstallContext\n---@field receipt InstallReceiptBuilder\n---@field fs InstallContextFs\n---@field location InstallLocation\n---@field spawn InstallContextSpawn\n---@field handle InstallHandle\n---@field public package AbstractPackage\n---@field cwd InstallContextCwd\n---@field opts PackageInstallOpts\n---@field stdio_sink StdioSink\n---@field links { bin: table<string, string>, share: table<string, string>, opt: table<string, string> }\nlocal InstallContext = {}\nInstallContext.__index = InstallContext\n\n---@param handle InstallHandle\n---@param opts PackageInstallOpts\nfunction InstallContext:new(handle, opts)\n    local cwd = InstallContextCwd:new(handle)\n    local spawn = InstallContextSpawn:new(handle, cwd, false)\n    local fs = InstallContextFs:new(cwd)\n    return setmetatable({\n        cwd = cwd,\n        spawn = spawn,\n        handle = handle,\n        location = handle.location, -- for convenience\n        package = handle.package, -- for convenience\n        fs = fs,\n        receipt = receipt.InstallReceiptBuilder:new(),\n        stdio_sink = handle.stdio_sink,\n        links = {\n            bin = {},\n            share = {},\n            opt = {},\n        },\n        opts = opts,\n    }, InstallContext)\nend\n\n---@async\n---@param url string\n---@param opts? FetchOpts\nfunction InstallContext:fetch(url, opts)\n    opts = opts or {}\n    if opts.out_file then\n        opts.out_file = path.concat { self.cwd:get(), opts.out_file }\n    end\n    return fetch(url, opts):get_or_throw()\nend\n\n---@async\nfunction InstallContext:promote_cwd()\n    local cwd = self.cwd:get()\n    local install_path = self:get_install_path()\n    if install_path == cwd then\n        log.fmt_debug(\"cwd %s is already promoted\", cwd)\n        return\n    end\n    log.fmt_debug(\"Promoting cwd %s to %s\", cwd, install_path)\n\n    -- 1. Uninstall any existing installation\n    if self.handle.package:is_installed() then\n        a.wait(function(resolve, reject)\n            self.handle.package:uninstall({ bypass_permit = true }, function(success, result)\n                if not success then\n                    reject(result)\n                else\n                    resolve()\n                end\n            end)\n        end)\n    end\n\n    -- 2. Prepare for renaming cwd to destination\n    if platform.is.unix then\n        -- Some Unix systems will raise an error when renaming a directory to a destination that does not already exist.\n        fs.sync.mkdir(install_path)\n    end\n    -- 3. Move the cwd to the final installation directory\n    local rename_success, rename_err = pcall(fs.sync.rename, cwd, install_path)\n    if not rename_success then\n        -- On some file systems, we cannot create the directory before renaming. Therefore, remove it and then rename.\n        log.trace(\"Call to uv_fs_rename() while promoting cwd failed.\", rename_err)\n        fs.sync.rmdir(install_path)\n        assert(fs.sync.dir_exists(cwd), \"Current working directory no longer exists after retrying uv_fs_rename().\")\n        fs.sync.rename(cwd, install_path)\n    end\n    -- 4. Update cwd\n    self.cwd:set(install_path)\nend\n\n---@param rel_path string The relative path from the current working directory to change cwd to. Will only restore to the initial cwd after execution of fn (if provided).\n---@param fn async (fun(): any)? The function to run in the context of the given path.\nfunction InstallContext:chdir(rel_path, fn)\n    local old_cwd = self.cwd:get()\n    self.cwd:set(path.concat { old_cwd, rel_path })\n    if fn then\n        local ok, result = pcall(fn)\n        self.cwd:set(old_cwd)\n        if not ok then\n            error(result, 0)\n        end\n        return result\n    end\nend\n\n---@async\n---@param fn fun(resolve: fun(result: any), reject: fun(error: any))\nfunction InstallContext:await(fn)\n    return a.wait(fn)\nend\n\n---@param new_executable_rel_path string Relative path to the executable file to create.\n---@param script_rel_path string Relative path to the Node.js script.\nfunction InstallContext:write_node_exec_wrapper(new_executable_rel_path, script_rel_path)\n    if not self.fs:file_exists(script_rel_path) then\n        error((\"Cannot write Node exec wrapper for path %q as it doesn't exist.\"):format(script_rel_path), 0)\n    end\n    return self:write_shell_exec_wrapper(\n        new_executable_rel_path,\n        (\"node %q\"):format(path.concat {\n            self:get_install_path(),\n            script_rel_path,\n        })\n    )\nend\n\n---@param new_executable_rel_path string Relative path to the executable file to create.\n---@param script_rel_path string Relative path to the Node.js script.\nfunction InstallContext:write_ruby_exec_wrapper(new_executable_rel_path, script_rel_path)\n    if not self.fs:file_exists(script_rel_path) then\n        error((\"Cannot write Ruby exec wrapper for path %q as it doesn't exist.\"):format(script_rel_path), 0)\n    end\n    return self:write_shell_exec_wrapper(\n        new_executable_rel_path,\n        (\"ruby %q\"):format(path.concat {\n            self:get_install_path(),\n            script_rel_path,\n        })\n    )\nend\n\n---@param new_executable_rel_path string Relative path to the executable file to create.\n---@param script_rel_path string Relative path to the PHP script.\nfunction InstallContext:write_php_exec_wrapper(new_executable_rel_path, script_rel_path)\n    if not self.fs:file_exists(script_rel_path) then\n        error((\"Cannot write PHP exec wrapper for path %q as it doesn't exist.\"):format(script_rel_path), 0)\n    end\n    return self:write_shell_exec_wrapper(\n        new_executable_rel_path,\n        (\"php %q\"):format(path.concat {\n            self:get_install_path(),\n            script_rel_path,\n        })\n    )\nend\n\n---@param new_executable_rel_path string Relative path to the executable file to create.\n---@param module string The python module to call.\nfunction InstallContext:write_pyvenv_exec_wrapper(new_executable_rel_path, module)\n    local pypi = require \"mason-core.installer.managers.pypi\"\n    local module_exists, module_err = pcall(function()\n        local result =\n            self.spawn.python { \"-c\", (\"import %s\"):format(module), with_paths = { pypi.venv_path(self.cwd:get()) } }\n        if not self.spawn.strict_mode then\n            result:get_or_throw()\n        end\n    end)\n    if not module_exists then\n        log.fmt_error(\"Failed to find module %q for package %q. %s\", module, self.package, module_err)\n        error((\"Cannot write Python exec wrapper for module %q as it doesn't exist.\"):format(module), 0)\n    end\n    return self:write_shell_exec_wrapper(\n        new_executable_rel_path,\n        (\"%q -m %s\"):format(\n            path.concat {\n                pypi.venv_path(self:get_install_path()),\n                \"python\",\n            },\n            module\n        )\n    )\nend\n\n---@param new_executable_rel_path string Relative path to the executable file to create.\n---@param target_executable_rel_path string\nfunction InstallContext:write_exec_wrapper(new_executable_rel_path, target_executable_rel_path)\n    if not self.fs:file_exists(target_executable_rel_path) then\n        error((\"Cannot write exec wrapper for path %q as it doesn't exist.\"):format(target_executable_rel_path), 0)\n    end\n    if platform.is.unix then\n        self.fs:chmod_exec(target_executable_rel_path)\n    end\n    return self:write_shell_exec_wrapper(\n        new_executable_rel_path,\n        (\"%q\"):format(path.concat {\n            self:get_install_path(),\n            target_executable_rel_path,\n        })\n    )\nend\n\nlocal BASH_TEMPLATE = _.dedent [[\n#!/usr/bin/env bash\n%s\nexec %s \"$@\"\n]]\n\nlocal BATCH_TEMPLATE = _.dedent [[\n@ECHO off\n%s\n%s %%*\n]]\n\n---@param new_executable_rel_path string Relative path to the executable file to create.\n---@param command string The shell command to run.\n---@param env table<string, string>?\n---@return string # The created executable filename.\nfunction InstallContext:write_shell_exec_wrapper(new_executable_rel_path, command, env)\n    if self.fs:file_exists(new_executable_rel_path) or self.fs:dir_exists(new_executable_rel_path) then\n        error((\"Cannot write exec wrapper to %q because the file already exists.\"):format(new_executable_rel_path), 0)\n    end\n    return platform.when {\n        unix = function()\n            local formatted_envs = _.map(function(pair)\n                local var, value = pair[1], pair[2]\n                return (\"export %s=%q\"):format(var, value)\n            end, _.to_pairs(env or {}))\n\n            self.fs:write_file(new_executable_rel_path, BASH_TEMPLATE:format(_.join(\"\\n\", formatted_envs), command))\n            self.fs:chmod_exec(new_executable_rel_path)\n            return new_executable_rel_path\n        end,\n        win = function()\n            local executable_file = (\"%s.cmd\"):format(new_executable_rel_path)\n            local formatted_envs = _.map(function(pair)\n                local var, value = pair[1], pair[2]\n                return (\"SET %s=%s\"):format(var, value)\n            end, _.to_pairs(env or {}))\n\n            self.fs:write_file(executable_file, BATCH_TEMPLATE:format(_.join(\"\\n\", formatted_envs), command))\n            return executable_file\n        end,\n    }\nend\n\n---@param executable string\n---@param rel_path string\nfunction InstallContext:link_bin(executable, rel_path)\n    self.links.bin[executable] = rel_path\n    return self\nend\n\nInstallContext.CONTEXT_REQUEST = {}\n\n---@generic T\n---@param fn fun(context: InstallContext): T\n---@return T\nfunction InstallContext:execute(fn)\n    local thread = coroutine.create(function(...)\n        -- We wrap the function to allow it to be a spy instance (in which case it's not actually a function, but a\n        -- callable metatable - coroutine.create strictly expects functions only)\n        return fn(...)\n    end)\n    local step\n    local ret_val\n    step = function(...)\n        local ok, result = coroutine.resume(thread, ...)\n        if not ok then\n            error(result, 0)\n        elseif result == InstallContext.CONTEXT_REQUEST then\n            step(self)\n        elseif coroutine.status(thread) == \"suspended\" then\n            -- yield to parent coroutine\n            step(coroutine.yield(result))\n        else\n            ret_val = result\n        end\n    end\n    step(self)\n    return ret_val\nend\n\n---@async\nfunction InstallContext:build_receipt()\n    log.fmt_debug(\"Building receipt for %s\", self.package)\n    return Result.pcall(function()\n        return self.receipt:with_name(self.package.name):with_completion_time(vim.loop.gettimeofday()):build()\n    end)\nend\n\nfunction InstallContext:get_install_path()\n    return self.location:package(self.package.name)\nend\n\nreturn InstallContext\n"
  },
  {
    "path": "lua/mason-core/installer/init.lua",
    "content": "local InstallContext = require \"mason-core.installer.context\"\n\nlocal M = {}\n\n---@return InstallContext\nfunction M.context()\n    return coroutine.yield(InstallContext.CONTEXT_REQUEST)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/linker.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal fs = require \"mason-core.fs\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal platform = require \"mason-core.platform\"\n\nlocal M = {}\n\n---@alias LinkContext { type: '\"bin\"' | '\"opt\"' | '\"share\"', prefix: fun(path: string, location: InstallLocation): string }\n\n---@type table<'\"BIN\"' | '\"OPT\"' | '\"SHARE\"', LinkContext>\nlocal LinkContext = {\n    BIN = {\n        type = \"bin\",\n        ---@param path string\n        ---@param location InstallLocation\n        prefix = function(path, location)\n            return location:bin(path)\n        end,\n    },\n    OPT = {\n        type = \"opt\",\n        ---@param path string\n        ---@param location InstallLocation\n        prefix = function(path, location)\n            return location:opt(path)\n        end,\n    },\n    SHARE = {\n        type = \"share\",\n        ---@param path string\n        ---@param location InstallLocation\n        prefix = function(path, location)\n            return location:share(path)\n        end,\n    },\n}\n\n---@param receipt InstallReceipt\n---@param link_context LinkContext\n---@param location InstallLocation\nlocal function unlink(receipt, link_context, location)\n    return Result.pcall(function()\n        local links = receipt:get_links()[link_context.type]\n        if not links then\n            return\n        end\n        for linked_file in pairs(links) do\n            if receipt:get_schema_version() == \"1.0\" and link_context == LinkContext.BIN and platform.is.win then\n                linked_file = linked_file .. \".cmd\"\n            end\n            local share_path = link_context.prefix(linked_file, location)\n            fs.sync.unlink(share_path)\n        end\n    end)\nend\n\n---@param pkg AbstractPackage\n---@param receipt InstallReceipt\n---@param location InstallLocation\n---@nodiscard\nfunction M.unlink(pkg, receipt, location)\n    log.fmt_debug(\"Unlinking %s\", pkg, receipt:get_links())\n    return Result.try(function(try)\n        try(unlink(receipt, LinkContext.BIN, location))\n        try(unlink(receipt, LinkContext.SHARE, location))\n        try(unlink(receipt, LinkContext.OPT, location))\n    end)\nend\n\n---@async\n---@param context InstallContext\n---@param link_context LinkContext\n---@param link_fn async fun(new_abs_path: string, target_abs_path: string, target_rel_path: string): Result\nlocal function link(context, link_context, link_fn)\n    log.trace(\"Linking\", context.package, link_context.type, context.links[link_context.type])\n    return Result.try(function(try)\n        for name, rel_path in pairs(context.links[link_context.type]) do\n            if platform.is.win and link_context == LinkContext.BIN then\n                name = (\"%s.cmd\"):format(name)\n            end\n            local new_abs_path = link_context.prefix(name, context.location)\n            local target_abs_path = path.concat { context:get_install_path(), rel_path }\n            local target_rel_path = path.relative(new_abs_path, target_abs_path)\n\n            -- 1. Ensure destination directory exists\n            a.scheduler()\n            local dir = vim.fn.fnamemodify(new_abs_path, \":h\")\n            if not fs.async.dir_exists(dir) then\n                try(Result.pcall(fs.async.mkdirp, dir))\n            end\n\n            -- 2. Ensure source file exists and target doesn't yet exist OR if --force unlink target if it already\n            -- exists.\n            if context.opts.force then\n                if fs.async.file_exists(new_abs_path) then\n                    try(Result.pcall(fs.async.unlink, new_abs_path))\n                end\n            elseif fs.async.file_exists(new_abs_path) then\n                return Result.failure((\"%q is already linked.\"):format(new_abs_path, name))\n            end\n            if not fs.async.file_exists(target_abs_path) then\n                return Result.failure((\"Link target %q does not exist.\"):format(target_abs_path))\n            end\n\n            -- 3. Execute link.\n            try(link_fn(new_abs_path, target_abs_path, target_rel_path))\n            context.receipt:with_link(link_context.type, name, rel_path)\n        end\n    end)\nend\n\n---@param context InstallContext\n---@param link_context LinkContext\nlocal function symlink(context, link_context)\n    return link(context, link_context, function(new_abs_path, _, target_rel_path)\n        return Result.pcall(fs.async.symlink, target_rel_path, new_abs_path)\n    end)\nend\n\n---@param context InstallContext\n---@param link_context LinkContext\nlocal function copyfile(context, link_context)\n    return link(context, link_context, function(new_abs_path, target_abs_path)\n        return Result.pcall(fs.async.copy_file, target_abs_path, new_abs_path, { excl = true })\n    end)\nend\n\n---@param context InstallContext\nlocal function win_bin_wrapper(context)\n    return link(context, LinkContext.BIN, function(new_abs_path, __, target_rel_path)\n        local windows_target_rel_path = target_rel_path:gsub(\"/\", \"\\\\\")\n        return Result.pcall(\n            fs.async.write_file,\n            new_abs_path,\n            _.dedent(([[\n                @ECHO off\n                GOTO start\n                :find_dp0\n                SET dp0=%%~dp0\n                EXIT /b\n                :start\n                SETLOCAL\n                CALL :find_dp0\n\n                endLocal & goto #_undefined_# 2>NUL || title %%COMSPEC%% & \"%%dp0%%\\%s\" %%*\n            ]]):format(windows_target_rel_path))\n        )\n    end)\nend\n\n---@async\n---@param context InstallContext\n---@nodiscard\nfunction M.link(context)\n    log.fmt_debug(\"Linking %s\", context.package)\n    return Result.try(function(try)\n        if platform.is.win then\n            try(win_bin_wrapper(context))\n            try(copyfile(context, LinkContext.SHARE))\n            try(copyfile(context, LinkContext.OPT))\n        else\n            try(symlink(context, LinkContext.BIN))\n            try(symlink(context, LinkContext.SHARE))\n            try(symlink(context, LinkContext.OPT))\n        end\n    end)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/managers/cargo.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal installer = require \"mason-core.installer\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal platform = require \"mason-core.platform\"\n\nlocal M = {}\n\n---@async\n---@param crate string\n---@param version string\n---@param opts? { features?: string, locked?: boolean, git?: { url: string, rev?: boolean } }\nfunction M.install(crate, version, opts)\n    opts = opts or {}\n    log.fmt_debug(\"cargo: install %s %s %s\", crate, version, opts)\n    local ctx = installer.context()\n    ctx.stdio_sink:stdout((\"Installing crate %s@%s…\\n\"):format(crate, version))\n    return ctx.spawn.cargo {\n        \"install\",\n        \"--root\",\n        \".\",\n        opts.git and {\n            \"--git\",\n            opts.git.url,\n            opts.git.rev and \"--rev\" or \"--tag\",\n            version,\n        } or { \"--version\", version },\n        opts.features and { \"--features\", opts.features } or vim.NIL,\n        opts.locked and \"--locked\" or vim.NIL,\n        crate,\n    }\nend\n\n---@param bin string\nfunction M.bin_path(bin)\n    return Result.pcall(platform.when, {\n        unix = function()\n            return path.concat { \"bin\", bin }\n        end,\n        win = function()\n            return path.concat { \"bin\", (\"%s.exe\"):format(bin) }\n        end,\n    })\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/managers/common.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal async_uv = require \"mason-core.async.uv\"\nlocal installer = require \"mason-core.installer\"\nlocal log = require \"mason-core.log\"\nlocal platform = require \"mason-core.platform\"\nlocal powershell = require \"mason-core.installer.managers.powershell\"\nlocal std = require \"mason-core.installer.managers.std\"\n\nlocal M = {}\n\n---@class DownloadItem\n---@field download_url string\n---@field out_file string\n\n---@class FileDownloadSpec\n---@field file string | string[]\n\nlocal get_source_file = _.compose(_.head, _.split \":\")\nlocal get_outfile = _.compose(_.last, _.split \":\")\n\n---Normalizes file paths from e.g. \"file:out-dir/\" to \"out-dir/file\".\n---@param file string\nlocal function normalize_file_path(file)\n    local source_file = get_source_file(file)\n    local new_path = get_outfile(file)\n\n    -- a dir expression (e.g. \"libexec/\")\n    if _.matches(\"/$\", new_path) then\n        return new_path .. source_file\n    end\n    return new_path\nend\n\n---@generic T : FileDownloadSpec\n---@type fun(download: T): T\nM.normalize_files = _.evolve {\n    file = _.cond {\n        { _.is \"string\", normalize_file_path },\n        { _.T, _.map(normalize_file_path) },\n    },\n}\n\n---@param download FileDownloadSpec\n---@param url_generator fun(file: string): string\n---@return DownloadItem[]\nfunction M.parse_downloads(download, url_generator)\n    local files = download.file\n    if type(files) == \"string\" then\n        files = { files }\n    end\n\n    return _.map(function(file)\n        local source_file = get_source_file(file)\n        local out_file = normalize_file_path(file)\n        return {\n            download_url = url_generator(source_file),\n            out_file = out_file,\n        }\n    end, files)\nend\n\n---@async\n---@param ctx InstallContext\n---@param downloads DownloadItem[]\n---@nodiscard\nfunction M.download_files(ctx, downloads)\n    return Result.try(function(try)\n        for __, download in ipairs(downloads) do\n            a.scheduler()\n            local out_dir = vim.fn.fnamemodify(download.out_file, \":h\")\n            local out_file = vim.fn.fnamemodify(download.out_file, \":t\")\n            if out_dir ~= \".\" then\n                try(Result.pcall(function()\n                    ctx.fs:mkdirp(out_dir)\n                end))\n            end\n            try(ctx:chdir(out_dir, function()\n                return Result.try(function(try)\n                    try(std.download_file(download.download_url, out_file))\n                    try(std.unpack(out_file))\n                end)\n            end))\n        end\n    end)\nend\n\n---@class BuildInstruction\n---@field target? Platform | Platform[]\n---@field run string\n---@field staged? boolean\n---@field env? table<string, string>\n\n---@async\n---@param build BuildInstruction\n---@return Result\n---@nodiscard\nfunction M.run_build_instruction(build)\n    log.fmt_debug(\"build: run %s\", build)\n    local ctx = installer.context()\n    if build.staged == false then\n        ctx:promote_cwd()\n    end\n    return platform.when {\n        unix = function()\n            return ctx.spawn.bash {\n                on_spawn = a.scope(function(_, stdio)\n                    local stdin = stdio[1]\n                    async_uv.write(stdin, \"set -euxo pipefail;\\n\")\n                    async_uv.write(stdin, build.run)\n                    async_uv.shutdown(stdin)\n                    async_uv.close(stdin)\n                end),\n                env = build.env,\n            }\n        end,\n        win = function()\n            return powershell.command(build.run, {\n                env = build.env,\n            }, ctx.spawn)\n        end,\n    }\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/managers/composer.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal installer = require \"mason-core.installer\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal platform = require \"mason-core.platform\"\n\nlocal M = {}\n\n---@async\n---@param package string\n---@param version string\n---@nodiscard\nfunction M.install(package, version)\n    log.fmt_debug(\"composer: install %s %s\", package, version)\n    local ctx = installer.context()\n    ctx.stdio_sink:stdout((\"Installing composer package %s@%s…\\n\"):format(package, version))\n    return Result.try(function(try)\n        try(ctx.spawn.composer {\n            \"init\",\n            \"--no-interaction\",\n            \"--stability=stable\",\n        })\n        try(ctx.spawn.composer {\n            \"require\",\n            (\"%s:%s\"):format(package, version),\n        })\n    end)\nend\n\n---@param executable string\nfunction M.bin_path(executable)\n    return Result.pcall(platform.when, {\n        unix = function()\n            return path.concat { \"vendor\", \"bin\", executable }\n        end,\n        win = function()\n            return path.concat { \"vendor\", \"bin\", (\"%s.bat\"):format(executable) }\n        end,\n    })\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/managers/gem.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal installer = require \"mason-core.installer\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal platform = require \"mason-core.platform\"\n\nlocal M = {}\n\n---@async\n---@param pkg string\n---@param version string\n---@param opts? { extra_packages?: string[] }\n---@nodiscard\nfunction M.install(pkg, version, opts)\n    opts = opts or {}\n    log.fmt_debug(\"gem: install %s %s %s\", pkg, version, opts)\n    local ctx = installer.context()\n    ctx.stdio_sink:stdout((\"Installing gem %s@%s…\\n\"):format(pkg, version))\n    return ctx.spawn.gem {\n        \"install\",\n        \"--no-user-install\",\n        \"--no-format-executable\",\n        \"--install-dir=.\",\n        \"--bindir=bin\",\n        \"--no-document\",\n        (\"%s:%s\"):format(pkg, version),\n        opts.extra_packages or vim.NIL,\n        env = {\n            GEM_HOME = ctx.cwd:get(),\n        },\n    }\nend\n\n---@async\n---@param bin string\n---@nodiscard\nfunction M.create_bin_wrapper(bin)\n    local ctx = installer.context()\n\n    local bin_path = platform.when {\n        unix = function()\n            return path.concat { \"bin\", bin }\n        end,\n        win = function()\n            return path.concat { \"bin\", (\"%s.bat\"):format(bin) }\n        end,\n    }\n\n    if not ctx.fs:file_exists(bin_path) then\n        return Result.failure((\"Cannot link Gem executable %q because it doesn't exist.\"):format(bin))\n    end\n\n    return Result.pcall(ctx.write_shell_exec_wrapper, ctx, bin, path.concat { ctx:get_install_path(), bin_path }, {\n        GEM_PATH = platform.when {\n            unix = function()\n                return (\"%s:$GEM_PATH\"):format(ctx:get_install_path())\n            end,\n            win = function()\n                return (\"%s;%%GEM_PATH%%\"):format(ctx:get_install_path())\n            end,\n        },\n    })\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/managers/golang.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal installer = require \"mason-core.installer\"\nlocal log = require \"mason-core.log\"\nlocal platform = require \"mason-core.platform\"\n\nlocal M = {}\n\n---@async\n---@param pkg string\n---@param version string\n---@param opts? { extra_packages?: string[] }\nfunction M.install(pkg, version, opts)\n    return Result.try(function(try)\n        opts = opts or {}\n        log.fmt_debug(\"golang: install %s %s %s\", pkg, version, opts)\n        local ctx = installer.context()\n        ctx.stdio_sink:stdout((\"Installing go package %s@%s…\\n\"):format(pkg, version))\n        local env = {\n            GOBIN = ctx.cwd:get(),\n        }\n        try(ctx.spawn.go {\n            \"install\",\n            \"-v\",\n            (\"%s@%s\"):format(pkg, version),\n            env = env,\n        })\n        if opts.extra_packages then\n            for _, pkg in ipairs(opts.extra_packages) do\n                try(ctx.spawn.go {\n                    \"install\",\n                    \"-v\",\n                    (\"%s@latest\"):format(pkg),\n                    env = env,\n                })\n            end\n        end\n    end)\nend\n\n---@param bin string\nfunction M.bin_path(bin)\n    return Result.pcall(platform.when, {\n        unix = function()\n            return bin\n        end,\n        win = function()\n            return (\"%s.exe\"):format(bin)\n        end,\n    })\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/managers/luarocks.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal installer = require \"mason-core.installer\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal platform = require \"mason-core.platform\"\n\nlocal M = {}\n\n---@async\n---@param pkg string\n---@param version string\n---@param opts { server?: string, dev?: boolean }\nfunction M.install(pkg, version, opts)\n    opts = opts or {}\n    log.fmt_debug(\"luarocks: install %s %s %s\", pkg, version, opts)\n    local ctx = installer.context()\n    ctx.stdio_sink:stdout((\"Installing luarocks package %s@%s…\\n\"):format(pkg, version))\n    ctx:promote_cwd() -- luarocks encodes absolute paths during installation\n    return ctx.spawn.luarocks {\n        \"install\",\n        { \"--tree\", ctx.cwd:get() },\n        opts.dev and \"--dev\" or vim.NIL,\n        opts.server and (\"--server=%s\"):format(opts.server) or vim.NIL,\n        { pkg, version },\n    }\nend\n\n---@param exec string\nfunction M.bin_path(exec)\n    return Result.pcall(platform.when, {\n        unix = function()\n            return path.concat { \"bin\", exec }\n        end,\n        win = function()\n            return path.concat { \"bin\", (\"%s.bat\"):format(exec) }\n        end,\n    })\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/managers/npm.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal installer = require \"mason-core.installer\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal platform = require \"mason-core.platform\"\nlocal semver = require \"mason-core.semver\"\nlocal spawn = require \"mason-core.spawn\"\n\nlocal M = {}\n\n---@async\n---@param predicate fun(npm_version: Semver): boolean\n---@return boolean\nlocal function npm_version_satisfies(predicate)\n    return Result.try(function(try)\n        local npm_versions = try(spawn.npm { \"version\", \"--json\" }).stdout\n        ---@type { npm: string }\n        local versions = try(Result.pcall(vim.json.decode, npm_versions))\n        ---@type Semver\n        local npm_version = try(semver.parse(versions.npm))\n        return predicate(npm_version)\n    end):get_or_else(false)\nend\n\n---@async\nfunction M.init()\n    log.debug \"npm: init\"\n    local ctx = installer.context()\n    return Result.try(function(try)\n        try(ctx.spawn.npm {\n            \"init\",\n            \"--yes\",\n            \"--scope=mason\",\n        })\n\n        -- Use shallow install-strategy. The reasons for this are:\n        --   a) To avoid polluting the executables (aka bin-links) that npm creates.\n        --   b) The installation is, after all, more similar to a \"global\" installation. We don't really gain\n        --      any of the benefits of not using global style (e.g., deduping the dependency tree).\n        --\n        --  We write to .npmrc manually instead of going through npm because managing a local .npmrc file\n        --  is a bit unreliable across npm versions (especially <7), so we take extra measures to avoid\n        --  inadvertently polluting global npm config.\n        try(Result.pcall(function()\n            if npm_version_satisfies(_.gte(semver.new \"9.0.0\")) then\n                ctx.fs:append_file(\".npmrc\", \"\\ninstall-strategy=shallow\")\n            else\n                ctx.fs:append_file(\".npmrc\", \"\\nglobal-style=true\")\n            end\n        end))\n\n        ctx.stdio_sink:stdout \"Initialized npm root.\\n\"\n    end)\nend\n\n---@async\n---@param pkg string\n---@param version string\n---@param opts? { extra_packages?: string[] }\nfunction M.install(pkg, version, opts)\n    opts = opts or {}\n    log.fmt_debug(\"npm: install %s %s %s\", pkg, version, opts)\n    local ctx = installer.context()\n    ctx.stdio_sink:stdout((\"Installing npm package %s@%s…\\n\"):format(pkg, version))\n    return ctx.spawn.npm {\n        \"install\",\n        (\"%s@%s\"):format(pkg, version),\n        opts.extra_packages or vim.NIL,\n    }\nend\n\n---@async\n---@param pkg string\nfunction M.uninstall(pkg)\n    local ctx = installer.context()\n    ctx.stdio_sink:stdout((\"Uninstalling npm package %s…\\n\"):format(pkg))\n    return ctx.spawn.npm { \"uninstall\", pkg }\nend\n\n---@param exec string\nfunction M.bin_path(exec)\n    return Result.pcall(platform.when, {\n        unix = function()\n            return path.concat { \"node_modules\", \".bin\", exec }\n        end,\n        win = function()\n            return path.concat { \"node_modules\", \".bin\", (\"%s.cmd\"):format(exec) }\n        end,\n    })\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/managers/nuget.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal installer = require \"mason-core.installer\"\nlocal log = require \"mason-core.log\"\nlocal platform = require \"mason-core.platform\"\n\nlocal M = {}\n\n---@async\n---@param package string\n---@param version string\n---@nodiscard\nfunction M.install(package, version)\n    log.fmt_debug(\"nuget: install %s %s\", package, version)\n    local ctx = installer.context()\n    ctx.stdio_sink:stdout((\"Installing nuget package %s@%s…\\n\"):format(package, version))\n    return ctx.spawn.dotnet {\n        \"tool\",\n        \"update\",\n        \"--tool-path\",\n        \".\",\n        { \"--version\", version },\n        package,\n    }\nend\n\n---@param bin string\nfunction M.bin_path(bin)\n    return Result.pcall(platform.when, {\n        unix = function()\n            return bin\n        end,\n        win = function()\n            return (\"%s.exe\"):format(bin)\n        end,\n    })\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/managers/opam.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal installer = require \"mason-core.installer\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal platform = require \"mason-core.platform\"\n\nlocal M = {}\n\n---@async\n---@param package string\n---@param version string\n---@nodiscard\nfunction M.install(package, version)\n    log.fmt_debug(\"opam: install %s %s\", package, version)\n    local ctx = installer.context()\n    ctx.stdio_sink:stdout((\"Installing opam package %s@%s…\\n\"):format(package, version))\n    return ctx.spawn.opam {\n        \"install\",\n        \"--destdir=.\",\n        \"--yes\",\n        \"--verbose\",\n        (\"%s.%s\"):format(package, version),\n    }\nend\n\n---@param bin string\nfunction M.bin_path(bin)\n    return Result.pcall(platform.when, {\n        unix = function()\n            return path.concat { \"bin\", bin }\n        end,\n        win = function()\n            return path.concat { \"bin\", (\"%s.exe\"):format(bin) }\n        end,\n    })\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/managers/powershell.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal process = require \"mason-core.process\"\nlocal spawn = require \"mason-core.spawn\"\n\nlocal M = {}\n\nlocal PWSHOPT = {\n    progress_preference = [[ $ProgressPreference = 'SilentlyContinue'; ]], -- https://stackoverflow.com/a/63301751\n    security_protocol = [[ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; ]],\n    error_action_preference = [[ $ErrorActionPreference = \"Stop\"; ]],\n}\n\nlocal powershell = _.lazy(function()\n    a.scheduler()\n    if vim.fn.executable \"pwsh\" == 1 then\n        return \"pwsh\"\n    else\n        return \"powershell\"\n    end\nend)\n\n---@async\n---@param command string\n---@param opts SpawnArgs?\n---@param custom_spawn JobSpawn?\nfunction M.command(command, opts, custom_spawn)\n    opts = opts or {}\n    ---@type JobSpawn\n    local spawner = custom_spawn or spawn\n    return spawner[powershell()](vim.tbl_extend(\"keep\", {\n        \"-NoProfile\",\n        \"-NonInteractive\",\n        \"-Command\",\n        PWSHOPT.error_action_preference .. PWSHOPT.progress_preference .. PWSHOPT.security_protocol .. command,\n        env_raw = process.graft_env(opts.env or {}, { \"PSMODULEPATH\" }),\n        on_spawn = function(_, stdio)\n            local stdin = stdio[1]\n            stdin:close()\n        end,\n    }, opts))\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/managers/pypi.lua",
    "content": "local Optional = require \"mason-core.optional\"\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal installer = require \"mason-core.installer\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal pep440 = require \"mason-core.pep440\"\nlocal platform = require \"mason-core.platform\"\nlocal providers = require \"mason-core.providers\"\nlocal semver = require \"mason-core.semver\"\nlocal spawn = require \"mason-core.spawn\"\n\nlocal M = {}\n\nlocal VENV_DIR = \"venv\"\n\nfunction M.venv_path(dir)\n    return path.concat {\n        dir,\n        VENV_DIR,\n        platform.is.win and \"Scripts\" or \"bin\",\n    }\nend\n\n---@async\n---@param candidates string[]\nlocal function resolve_python3(candidates)\n    local is_executable = _.compose(_.equals(1), vim.fn.executable)\n    a.scheduler()\n    local available_candidates = _.filter(is_executable, candidates)\n    for __, candidate in ipairs(available_candidates) do\n        ---@type string\n        local version_output = spawn[candidate]({ \"--version\" }):map(_.prop \"stdout\"):get_or_else \"\"\n        local ok, version = pcall(semver.new, version_output:match \"Python (3%.%d+.%d+)\")\n        if ok then\n            return { executable = candidate, version = version }\n        end\n    end\n    return nil\nend\n\n---@param version string\n---@param specifiers string\nlocal function pep440_check_version(version, specifiers)\n    -- The version check only implements a subset of the PEP440 specification and may error with certain inputs.\n    local ok, result = pcall(pep440.check_version, version, specifiers)\n    if not ok then\n        log.fmt_warn(\n            \"Failed to check PEP440 version compatibility for version %s with specifiers %s: %s\",\n            version,\n            specifiers,\n            result\n        )\n        return false\n    end\n    return result\nend\n\n---@param supported_python_versions string\nlocal function get_versioned_candidates(supported_python_versions)\n    return _.filter_map(function(pair)\n        local version, executable = unpack(pair)\n        if not pep440_check_version(tostring(version), supported_python_versions) then\n            return Optional.empty()\n        end\n        return Optional.of(executable)\n    end, {\n        { semver.new \"3.12.0\", \"python3.12\" },\n        { semver.new \"3.11.0\", \"python3.11\" },\n        { semver.new \"3.10.0\", \"python3.10\" },\n        { semver.new \"3.9.0\", \"python3.9\" },\n        { semver.new \"3.8.0\", \"python3.8\" },\n        { semver.new \"3.7.0\", \"python3.7\" },\n        { semver.new \"3.6.0\", \"python3.6\" },\n    })\nend\n\n---@async\n---@param pkg { name: string, version: string }\nlocal function create_venv(pkg)\n    local ctx = installer.context()\n    ---@type string?\n    local supported_python_versions = providers.pypi.get_supported_python_versions(pkg.name, pkg.version):get_or_nil()\n\n    -- 1. Resolve stock python3 installation.\n    local stock_candidates = platform.is.win and { \"python\", \"python3\" } or { \"python3\", \"python\" }\n    local stock_target = resolve_python3(stock_candidates)\n    if stock_target then\n        log.fmt_debug(\"Resolved stock python3 installation version %s\", stock_target.version)\n    end\n\n    -- 2. Resolve suitable versioned python3 installation (python3.12, python3.11, etc.).\n    local versioned_candidates = {}\n    if supported_python_versions ~= nil then\n        if stock_target and not pep440_check_version(tostring(stock_target.version), supported_python_versions) then\n            log.fmt_debug(\"Finding versioned candidates for %s\", supported_python_versions)\n            versioned_candidates = get_versioned_candidates(supported_python_versions)\n        end\n    end\n    local target = resolve_python3(versioned_candidates) or stock_target\n\n    if not target then\n        return Result.failure(\n            (\"Unable to find python3 installation in PATH. Tried the following candidates: %s.\"):format(\n                _.join(\", \", _.concat(stock_candidates, versioned_candidates))\n            )\n        )\n    end\n\n    -- 3. If a versioned python3 installation was not found, warn the user if the stock python3 installation is outside\n    -- the supported version range.\n    if\n        target == stock_target\n        and supported_python_versions ~= nil\n        and not pep440_check_version(tostring(target.version), supported_python_versions)\n    then\n        if ctx.opts.force then\n            ctx.stdio_sink:stderr(\n                (\"Warning: The resolved python3 version %s is not compatible with the required Python versions: %s.\\n\"):format(\n                    target.version,\n                    supported_python_versions\n                )\n            )\n        else\n            ctx.stdio_sink:stderr \"Run with :MasonInstall --force to bypass this version validation.\\n\"\n            return Result.failure(\n                (\"Failed to find a python3 installation in PATH that meets the required versions (%s). Found version: %s.\"):format(\n                    supported_python_versions,\n                    target.version\n                )\n            )\n        end\n    end\n\n    log.fmt_debug(\"Found python3 installation version=%s, executable=%s\", target.version, target.executable)\n    ctx.stdio_sink:stdout \"Creating virtual environment…\\n\"\n    return ctx.spawn[target.executable] { \"-m\", \"venv\", \"--system-site-packages\", VENV_DIR }\nend\n\n---@param ctx InstallContext\n---@param executable string\nlocal function find_venv_executable(ctx, executable)\n    local candidates = _.filter(_.identity, {\n        platform.is.unix and path.concat { VENV_DIR, \"bin\", executable },\n        -- MSYS2\n        platform.is.win and path.concat { VENV_DIR, \"bin\", (\"%s.exe\"):format(executable) },\n        -- Stock Windows\n        platform.is.win and path.concat { VENV_DIR, \"Scripts\", (\"%s.exe\"):format(executable) },\n    })\n\n    for _, candidate in ipairs(candidates) do\n        if ctx.fs:file_exists(candidate) then\n            return Result.success(candidate)\n        end\n    end\n    return Result.failure((\"Failed to find executable %q in Python virtual environment.\"):format(executable))\nend\n\n---@async\n---@param args SpawnArgs\nlocal function venv_python(args)\n    local ctx = installer.context()\n    return find_venv_executable(ctx, \"python\"):and_then(function(python_path)\n        return ctx.spawn[path.concat { ctx.cwd:get(), python_path }](args)\n    end)\nend\n\n---@async\n---@param pkgs string[]\n---@param extra_args? string[]\nlocal function pip_install(pkgs, extra_args)\n    return venv_python {\n        \"-m\",\n        \"pip\",\n        \"--disable-pip-version-check\",\n        \"install\",\n        \"--no-user\",\n        \"--ignore-installed\",\n        extra_args or vim.NIL,\n        pkgs,\n    }\nend\n\n---@async\n---@param opts { package: { name: string, version: string }, upgrade_pip: boolean, install_extra_args?: string[] }\nfunction M.init(opts)\n    return Result.try(function(try)\n        log.fmt_debug(\"pypi: init\", opts)\n        local ctx = installer.context()\n\n        -- pip3 will hardcode the full path to venv executables, so we need to promote cwd to make sure pip uses the final destination path.\n        ctx:promote_cwd()\n        try(create_venv(opts.package))\n\n        if opts.upgrade_pip then\n            ctx.stdio_sink:stdout \"Upgrading pip inside the virtual environment…\\n\"\n            try(pip_install({ \"pip\" }, opts.install_extra_args))\n        end\n    end)\nend\n\n---@async\n---@param pkg string\n---@param version string\n---@param opts? { extra?: string, extra_packages?: string[], install_extra_args?: string[] }\nfunction M.install(pkg, version, opts)\n    opts = opts or {}\n    log.fmt_debug(\"pypi: install %s %s %s\", pkg, version, opts or \"\")\n    local ctx = installer.context()\n    ctx.stdio_sink:stdout((\"Installing pip package %s@%s…\\n\"):format(pkg, version))\n    return pip_install({\n        opts.extra and (\"%s[%s]==%s\"):format(pkg, opts.extra, version) or (\"%s==%s\"):format(pkg, version),\n        opts.extra_packages or vim.NIL,\n    }, opts.install_extra_args)\nend\n\n---@async\n---@param pkg string\nfunction M.uninstall(pkg)\n    log.fmt_debug(\"pypi: uninstall %s\", pkg)\n    return venv_python {\n        \"-m\",\n        \"pip\",\n        \"uninstall\",\n        \"-y\",\n        pkg,\n    }\nend\n\n---@param executable string\nfunction M.bin_path(executable)\n    local ctx = installer.context()\n    return find_venv_executable(ctx, executable)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/installer/managers/std.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal fetch = require \"mason-core.fetch\"\nlocal installer = require \"mason-core.installer\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal platform = require \"mason-core.platform\"\nlocal powershell = require \"mason-core.installer.managers.powershell\"\n\nlocal M = {}\n\n---@async\n---@param rel_path string\n---@nodiscard\nlocal function unpack_7z(rel_path)\n    log.fmt_debug(\"std: unpack_7z %s\", rel_path)\n    local ctx = installer.context()\n    return ctx.spawn[\"7z\"] { \"x\", \"-y\", \"-r\", rel_path }\nend\n\n---@async\n---@param rel_path string\n---@nodiscard\nlocal function unpack_peazip(rel_path)\n    log.fmt_debug(\"std: unpack_peazip %s\", rel_path)\n    local ctx = installer.context()\n    return ctx.spawn.peazip { \"-ext2here\", path.concat { ctx.cwd:get(), rel_path } } -- peazip requires absolute paths\nend\n\n---@async\n---@param rel_path string\n---@nodiscard\nlocal function wzunzip(rel_path)\n    log.fmt_debug(\"std: wzunzip %s\", rel_path)\n    local ctx = installer.context()\n    return ctx.spawn.wzunzip { rel_path }\nend\n\n---@async\n---@param rel_path string\n---@nodiscard\nlocal function unpack_winrar(rel_path)\n    log.fmt_debug(\"std: unpack_winrar %s\", rel_path)\n    local ctx = installer.context()\n    return ctx.spawn.winrar { \"e\", rel_path }\nend\n\n---@async\n---@param rel_path string\n---@nodiscard\nlocal function gunzip_unix(rel_path)\n    log.fmt_debug(\"std: gunzip_unix %s\", rel_path)\n    local ctx = installer.context()\n    return ctx.spawn.gzip { \"-d\", rel_path }\nend\n\n---@async\n---@param rel_path string\n---@nodiscard\nlocal function unpack_arc(rel_path)\n    log.fmt_debug(\"std: unpack_arc %s\", rel_path)\n    local ctx = installer.context()\n    return ctx.spawn.arc { \"unarchive\", rel_path }\nend\n\n---@param rel_path string\n---@return Result\nlocal function win_decompress(rel_path)\n    local ctx = installer.context()\n    return gunzip_unix(rel_path)\n        :or_else(function()\n            return unpack_7z(rel_path)\n        end)\n        :or_else(function()\n            return unpack_peazip(rel_path)\n        end)\n        :or_else(function()\n            return wzunzip(rel_path)\n        end)\n        :or_else(function()\n            return unpack_winrar(rel_path)\n        end)\n        :on_success(function()\n            pcall(function()\n                ctx.fs:unlink(rel_path)\n            end)\n        end)\nend\n\n---@async\n---@param url string\n---@param out_file string\n---@nodiscard\nfunction M.download_file(url, out_file)\n    log.fmt_debug(\"std: downloading file %s\", url, out_file)\n    local ctx = installer.context()\n    ctx.stdio_sink:stdout((\"Downloading file %q…\\n\"):format(url))\n    return fetch(url, {\n        out_file = path.concat { ctx.cwd:get(), out_file },\n    }):map_err(function(err)\n        return (\"%s\\nFailed to download file %q.\"):format(err, url)\n    end)\nend\n\n---@async\n---@param rel_path string\n---@nodiscard\nlocal function untar(rel_path)\n    log.fmt_debug(\"std: untar %s\", rel_path)\n    local ctx = installer.context()\n    a.scheduler()\n    local tar = vim.fn.executable \"gtar\" == 1 and \"gtar\" or \"tar\"\n    return ctx.spawn[tar]({ \"--no-same-owner\", \"-xvf\", rel_path }):on_success(function()\n        pcall(function()\n            ctx.fs:unlink(rel_path)\n        end)\n    end)\nend\n\n---@async\n---@param rel_path string\n---@nodiscard\nlocal function unzip(rel_path)\n    log.fmt_debug(\"std: unzip %s\", rel_path)\n    local ctx = installer.context()\n    return platform.when {\n        unix = function()\n            return ctx.spawn.unzip({ \"-d\", \".\", rel_path }):on_success(function()\n                pcall(function()\n                    ctx.fs:unlink(rel_path)\n                end)\n            end)\n        end,\n        win = function()\n            return Result.pcall(function()\n                -- Expand-Archive seems to be hard-coded to only allow .zip extensions. Bit weird but ok.\n                if not _.matches(\"%.zip$\", rel_path) then\n                    local zip_file = (\"%s.zip\"):format(rel_path)\n                    ctx.fs:rename(rel_path, zip_file)\n                    return zip_file\n                end\n                return rel_path\n            end):and_then(function(zip_file)\n                return powershell\n                    .command(\n                        (\"Microsoft.PowerShell.Archive\\\\Expand-Archive -Path %q -DestinationPath .\"):format(zip_file),\n                        {},\n                        ctx.spawn\n                    )\n                    :on_success(function()\n                        pcall(function()\n                            ctx.fs:unlink(zip_file)\n                        end)\n                    end)\n            end)\n        end,\n    }\nend\n\n---@async\n---@param rel_path string\n---@nodiscard\nlocal function gunzip(rel_path)\n    log.fmt_debug(\"std: gunzip %s\", rel_path)\n    return platform.when {\n        unix = function()\n            return gunzip_unix(rel_path)\n        end,\n        win = function()\n            return win_decompress(rel_path)\n        end,\n    }\nend\n\n---@async\n---@param rel_path string\n---@return Result\n---@nodiscard\nlocal function untar_compressed(rel_path)\n    log.fmt_debug(\"std: untar_compressed %s\", rel_path)\n    return platform.when {\n        unix = function()\n            return untar(rel_path)\n        end,\n        win = function()\n            return win_decompress(rel_path)\n                :and_then(function()\n                    return untar(_.gsub(\"%.tar%..*$\", \".tar\", rel_path))\n                end)\n                :or_else(function()\n                    -- arc both decompresses and unpacks tar in one go\n                    return unpack_arc(rel_path)\n                end)\n        end,\n    }\nend\n\n---@async\n---@param rel_path string\n---@return Result\n---@nodiscard\nlocal function untar_zst(rel_path)\n    return platform.when {\n        unix = function()\n            return untar(rel_path)\n        end,\n        win = function()\n            local ctx = installer.context()\n            local uncompressed_tar = rel_path:gsub(\"%.zst$\", \"\")\n            ctx.spawn.zstd { \"-dfo\", uncompressed_tar, rel_path }\n            ctx.fs:unlink(rel_path)\n            return untar(uncompressed_tar)\n        end,\n    }\nend\n\n-- Order is important.\nlocal unpack_by_filename = _.cond {\n    { _.matches \"%.tar$\", untar },\n    { _.matches \"%.tar%.gz$\", untar },\n    { _.matches \"%.tar%.bz2$\", untar },\n    { _.matches \"%.tar%.xz$\", untar_compressed },\n    { _.matches \"%.tar%.zst$\", untar_zst },\n    { _.matches \"%.zip$\", unzip },\n    { _.matches \"%.vsix$\", unzip },\n    { _.matches \"%.gz$\", gunzip },\n    { _.T, _.compose(Result.success, _.format \"%q doesn't need unpacking.\") },\n}\n\n---@async\n---@param rel_path string The relative path to the file to unpack.\n---@nodiscard\nfunction M.unpack(rel_path)\n    log.fmt_debug(\"std: unpack %s\", rel_path)\n    local ctx = installer.context()\n    ctx.stdio_sink:stdout(((\"Unpacking %q…\\n\"):format(rel_path)))\n    return unpack_by_filename(rel_path)\nend\n\n---@async\n---@param git_url string\n---@param opts? { rev?: string, recursive?: boolean }\n---@nodiscard\nfunction M.clone(git_url, opts)\n    opts = opts or {}\n    log.fmt_debug(\"std: clone %s %s\", git_url, opts)\n    local ctx = installer.context()\n    ctx.stdio_sink:stdout(((\"Cloning git repository %q…\\n\"):format(git_url)))\n    return Result.try(function(try)\n        try(ctx.spawn.git {\n            \"clone\",\n            \"--depth\",\n            \"1\",\n            opts.recursive and \"--recursive\" or vim.NIL,\n            git_url,\n            \".\",\n        })\n        if opts.rev then\n            try(ctx.spawn.git { \"fetch\", \"--depth\", \"1\", \"origin\", opts.rev })\n            try(ctx.spawn.git { \"checkout\", \"--quiet\", \"FETCH_HEAD\" })\n        end\n    end)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/log.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal path = require \"mason-core.path\"\nlocal settings = require \"mason.settings\"\n\nlocal config = {\n    -- Name of the plugin. Prepended to log messages\n    name = \"mason\",\n\n    -- Should print the output to neovim while running\n    -- values: 'sync','async',false\n    use_console = vim.env.MASON_VERBOSE_LOGS == \"1\",\n\n    -- Should highlighting be used in console (using echohl)\n    highlights = true,\n\n    -- Should write to a file\n    use_file = true,\n\n    -- Level configuration\n    modes = {\n        { name = \"trace\", hl = \"Comment\", level = vim.log.levels.TRACE },\n        { name = \"debug\", hl = \"Comment\", level = vim.log.levels.DEBUG },\n        { name = \"info\", hl = \"None\", level = vim.log.levels.INFO },\n        { name = \"warn\", hl = \"WarningMsg\", level = vim.log.levels.WARN },\n        { name = \"error\", hl = \"ErrorMsg\", level = vim.log.levels.ERROR },\n    },\n\n    -- Can limit the number of decimals displayed for floats\n    float_precision = 0.01,\n}\n\nlocal log = {\n    outfile = path.concat {\n        (vim.fn.has \"nvim-0.8.0\" == 1) and vim.fn.stdpath \"log\" or vim.fn.stdpath \"cache\",\n        (\"%s.log\"):format(config.name),\n    },\n}\n\n-- selene: allow(incorrect_standard_library_use)\nlocal unpack = unpack or table.unpack\n\ndo\n    local round = function(x, increment)\n        increment = increment or 1\n        x = x / increment\n        return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment\n    end\n\n    local tbl_has_tostring = function(tbl)\n        local mt = getmetatable(tbl)\n        return mt and mt.__tostring ~= nil\n    end\n\n    local make_string = function(...)\n        local t = {}\n        for i = 1, select(\"#\", ...) do\n            local x = select(i, ...)\n\n            if type(x) == \"number\" and config.float_precision then\n                x = tostring(round(x, config.float_precision))\n            elseif type(x) == \"table\" and not tbl_has_tostring(x) then\n                x = vim.inspect(x)\n            else\n                x = tostring(x)\n            end\n\n            t[#t + 1] = x\n        end\n        return table.concat(t, \" \")\n    end\n\n    local log_at_level = function(level_config, message_maker, ...)\n        -- Return early if we're below the current_log_level\n        if level_config.level < settings.current.log_level then\n            return\n        end\n        local nameupper = level_config.name:upper()\n\n        local msg = message_maker(...)\n        local info = debug.getinfo(config.info_level or 2, \"Sl\")\n        local lineinfo = info.short_src .. \":\" .. info.currentline\n\n        -- Output to console\n        if config.use_console then\n            local log_to_console = function()\n                local console_string = string.format(\"[%-6s%s] %s: %s\", nameupper, os.date \"%H:%M:%S\", lineinfo, msg)\n\n                if config.highlights and level_config.hl then\n                    vim.cmd(string.format(\"echohl %s\", level_config.hl))\n                end\n\n                local split_console = vim.split(console_string, \"\\n\")\n                for _, v in ipairs(split_console) do\n                    local formatted_msg = string.format(\"[%s] %s\", config.name, vim.fn.escape(v, [[\"\\]]))\n\n                    local ok = pcall(vim.cmd, string.format([[echom \"%s\"]], formatted_msg))\n                    if not ok then\n                        vim.api.nvim_out_write(msg .. \"\\n\")\n                    end\n                end\n\n                if config.highlights and level_config.hl then\n                    vim.cmd \"echohl NONE\"\n                end\n            end\n            if config.use_console == \"sync\" and not vim.in_fast_event() then\n                log_to_console()\n            else\n                vim.schedule(log_to_console)\n            end\n        end\n\n        -- Output to log file\n        if config.use_file then\n            local fp = assert(io.open(log.outfile, \"a\"))\n            local str = string.format(\"[%-6s%s] %s: %s\\n\", nameupper, os.date(), lineinfo, msg)\n            fp:write(str)\n            fp:close()\n        end\n    end\n\n    for __, x in ipairs(config.modes) do\n        -- log.info(\"these\", \"are\", \"separated\")\n        log[x.name] = function(...)\n            return log_at_level(x, make_string, ...)\n        end\n\n        -- log.fmt_info(\"These are %s strings\", \"formatted\")\n        log[(\"fmt_%s\"):format(x.name)] = function(...)\n            return log_at_level(x, function(...)\n                local passed = { ... }\n                local fmt = table.remove(passed, 1)\n                local inspected = {}\n                for _, v in ipairs(passed) do\n                    if type(v) == \"table\" and tbl_has_tostring(v) then\n                        table.insert(inspected, v)\n                    else\n                        table.insert(inspected, vim.inspect(v))\n                    end\n                end\n                return string.format(fmt, unpack(inspected))\n            end, ...)\n        end\n\n        -- log.lazy_info(expensive_to_calculate)\n        log[(\"lazy_%s\"):format(x.name)] = function(f)\n            return log_at_level(x, function()\n                local passed = _.table_pack(f())\n                local fmt = table.remove(passed, 1)\n                local inspected = {}\n                for _, v in ipairs(passed) do\n                    if type(v) == \"table\" and tbl_has_tostring(v) then\n                        table.insert(inspected, v)\n                    else\n                        table.insert(inspected, vim.inspect(v))\n                    end\n                end\n                return string.format(fmt, unpack(inspected))\n            end)\n        end\n\n        -- log.file_info(\"do not print\")\n        log[(\"file_%s\"):format(x.name)] = function(vals, override)\n            local original_console = config.use_console\n            config.use_console = false\n            config.info_level = override.info_level\n            log_at_level(x, make_string, unpack(vals))\n            config.use_console = original_console\n            config.info_level = nil\n        end\n    end\nend\n\nreturn log\n"
  },
  {
    "path": "lua/mason-core/notify.lua",
    "content": "local TITLE = \"mason.nvim\"\n\nreturn function(msg, level)\n    level = level or vim.log.levels.INFO\n    vim.notify(msg, level, {\n        title = TITLE,\n    })\nend\n"
  },
  {
    "path": "lua/mason-core/optional.lua",
    "content": "---@class Optional<T>\n---@field private _value unknown\nlocal Optional = {}\nOptional.__index = Optional\n\n---@param value any\nfunction Optional:new(value)\n    ---@type Optional\n    local instance = {}\n    setmetatable(instance, self)\n    instance._value = value\n    return instance\nend\n\nlocal EMPTY = Optional:new(nil)\n\n---@param value any\nfunction Optional.of_nilable(value)\n    if value == nil then\n        return EMPTY\n    else\n        return Optional:new(value)\n    end\nend\n\nfunction Optional.empty()\n    return EMPTY\nend\n\n---@param value any\nfunction Optional.of(value)\n    return Optional:new(value)\nend\n\n---@param mapper_fn fun(value: any): any\nfunction Optional:map(mapper_fn)\n    if self:is_present() then\n        return Optional.of_nilable(mapper_fn(self._value))\n    else\n        return EMPTY\n    end\nend\n\nfunction Optional:get()\n    if not self:is_present() then\n        error(\"No value present.\", 2)\n    end\n    return self._value\nend\n\n---@param value any\nfunction Optional:or_else(value)\n    if self:is_present() then\n        return self._value\n    else\n        return value\n    end\nend\n\n---@param supplier fun(): any\nfunction Optional:or_else_get(supplier)\n    if self:is_present() then\n        return self._value\n    else\n        return supplier()\n    end\nend\n\n---@param supplier fun(value: any): Optional\n---@return Optional\nfunction Optional:and_then(supplier)\n    if self:is_present() then\n        return supplier(self._value)\n    else\n        return self\n    end\nend\n\n---@param supplier fun(): Optional\n---@return Optional\nfunction Optional:or_(supplier)\n    if self:is_present() then\n        return self\n    else\n        return supplier()\n    end\nend\n\n---@param exception any? The exception to throw if the result is a failure.\nfunction Optional:or_else_throw(exception)\n    if self:is_present() then\n        return self._value\n    else\n        if exception then\n            error(exception, 2)\n        else\n            error(\"No value present.\", 2)\n        end\n    end\nend\n\n---@param fn fun(value: any)\nfunction Optional:if_present(fn)\n    if self:is_present() then\n        fn(self._value)\n    end\n    return self\nend\n\n---@param fn fun(value: any)\nfunction Optional:if_not_present(fn)\n    if not self:is_present() then\n        fn(self._value)\n    end\n    return self\nend\n\nfunction Optional:is_present()\n    return self._value ~= nil\nend\n\n---@param err (fun(): any)|string\nfunction Optional:ok_or(err)\n    local Result = require \"mason-core.result\"\n    if self:is_present() then\n        return Result.success(self:get())\n    else\n        if type(err) == \"string\" then\n            return Result.failure(err)\n        else\n            return Result.failure(err())\n        end\n    end\nend\n\nreturn Optional\n"
  },
  {
    "path": "lua/mason-core/package/AbstractPackage.lua",
    "content": "local EventEmitter = require \"mason-core.EventEmitter\"\nlocal InstallLocation = require \"mason-core.installer.InstallLocation\"\nlocal Optional = require \"mason-core.optional\"\nlocal Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal fs = require \"mason-core.fs\"\nlocal log = require \"mason-core.log\"\nlocal settings = require \"mason.settings\"\nlocal Semaphore = require(\"mason-core.async.control\").Semaphore\n\n---@alias PackageInstallOpts { version?: string, debug?: boolean, target?: string, force?: boolean, strict?: boolean, location?: InstallLocation }\n---@alias PackageUninstallOpts { bypass_permit?: boolean, location?: InstallLocation }\n\n---@class AbstractPackage : EventEmitter\n---@field name string\n---@field spec RegistryPackageSpec\n---@field registry RegistrySource\n---@field private install_handle InstallHandle? The currently associated installation handle.\n---@field private uninstall_handle InstallHandle? The currently associated uninstallation handle.\nlocal AbstractPackage = {}\nAbstractPackage.__index = AbstractPackage\nsetmetatable(AbstractPackage, { __index = EventEmitter })\n\nAbstractPackage.SEMAPHORE = Semaphore:new(settings.current.max_concurrent_installers)\n---@type PackageInstallOpts\nAbstractPackage.DEFAULT_INSTALL_OPTS = {\n    debug = false,\n    force = false,\n    strict = false,\n    target = nil,\n    version = nil,\n}\n\n---@param spec RegistryPackageSpec\n---@param reg RegistrySource\nfunction AbstractPackage:new(spec, reg)\n    local instance = EventEmitter.new(self)\n    instance.name = spec.name -- for convenient access\n    instance.spec = spec\n    instance.registry = reg\n    return instance\nend\n\n---@param spec RegistryPackageSpec\n---@param reg RegistrySource\nfunction AbstractPackage:update(spec, reg)\n    self.name = spec.name -- shouldn't be necessary but might as well\n    self.spec = spec\n    self.registry = reg\n    return self\nend\n\n---@return boolean\nfunction AbstractPackage:is_installing()\n    return self:get_install_handle()\n        :map(\n            ---@param handle InstallHandle\n            function(handle)\n                return not handle:is_closed()\n            end\n        )\n        :or_else(false)\nend\n\n---@return boolean\nfunction AbstractPackage:is_uninstalling()\n    return self:get_uninstall_handle()\n        :map(\n            ---@param handle InstallHandle\n            function(handle)\n                return not handle:is_closed()\n            end\n        )\n        :or_else(false)\nend\n\nfunction AbstractPackage:get_install_handle()\n    return Optional.of_nilable(self.install_handle)\nend\n\nfunction AbstractPackage:get_uninstall_handle()\n    return Optional.of_nilable(self.uninstall_handle)\nend\n\n---@param location InstallLocation\nfunction AbstractPackage:new_handle(location)\n    assert(location, \"Cannot create new handle without a location.\")\n    local InstallHandle = require \"mason-core.installer.InstallHandle\"\n    local handle = InstallHandle:new(self, location)\n    -- Ideally we'd decouple this and leverage Mason's event system, but to allow loading as little as possible during\n    -- setup (i.e. not load modules related to Mason's event system) of the mason.nvim plugin we explicitly call into\n    -- terminator here.\n    require(\"mason-core.terminator\").register(handle)\n    return handle\nend\n\n---@param location? InstallLocation\nfunction AbstractPackage:new_install_handle(location)\n    location = location or InstallLocation.global()\n    log.fmt_trace(\"Creating new installation handle for %s\", self)\n    self:get_install_handle():if_present(function(handle)\n        assert(handle:is_closed(), \"Cannot create new install handle because existing handle is not closed.\")\n    end)\n    self.install_handle = self:new_handle(location)\n    self:emit(\"install:handle\", self.install_handle)\n    return self.install_handle\nend\n\n---@param location? InstallLocation\nfunction AbstractPackage:new_uninstall_handle(location)\n    location = location or InstallLocation.global()\n    log.fmt_trace(\"Creating new uninstallation handle for %s\", self)\n    self:get_uninstall_handle():if_present(function(handle)\n        assert(handle:is_closed(), \"Cannot create new uninstall handle because existing handle is not closed.\")\n    end)\n    self.uninstall_handle = self:new_handle(location)\n    self:emit(\"uninstall:handle\", self.uninstall_handle)\n    return self.uninstall_handle\nend\n\n---@param opts? PackageInstallOpts\nfunction AbstractPackage:is_installable(opts)\n    return require(\"mason-core.installer.compiler\").parse(self.spec, opts or {}):is_success()\nend\n\n---@param location? InstallLocation\n---@return Optional # Optional<InstallReceipt>\nfunction AbstractPackage:get_receipt(location)\n    location = location or InstallLocation.global()\n    local receipt_path = location:receipt(self.name)\n    if fs.sync.file_exists(receipt_path) then\n        local receipt = require \"mason-core.receipt\"\n        return Optional.of(receipt.InstallReceipt.from_json(vim.json.decode(fs.sync.read_file(receipt_path))))\n    end\n    return Optional.empty()\nend\n\n---@param location? InstallLocation\n---@return boolean\nfunction AbstractPackage:is_installed(location)\n    error \"Unimplemented.\"\nend\n\n---@return Result # Result<string[]>\nfunction AbstractPackage:get_all_versions()\n    local compiler = require \"mason-core.installer.compiler\"\n    return Result.try(function(try)\n        ---@type Purl\n        local purl = try(Purl.parse(self.spec.source.id))\n        ---@type InstallerCompiler\n        local compiler = try(compiler.get_compiler(purl))\n        return compiler.get_versions(purl, self.spec.source)\n    end)\nend\n\n---@return string\nfunction AbstractPackage:get_latest_version()\n    return Purl.parse(self.spec.source.id)\n        :map(_.prop \"version\")\n        :get_or_throw((\"Unable to retrieve version from malformed purl: %s.\"):format(self.spec.source.id))\nend\n\n---@param location? InstallLocation\n---@return string?\nfunction AbstractPackage:get_installed_version(location)\n    return self:get_receipt(location)\n        :map(\n            ---@param receipt InstallReceipt\n            function(receipt)\n                return receipt:get_installed_package_version()\n            end\n        )\n        :or_else(nil)\nend\n\n---@param opts? PackageInstallOpts\n---@param callback? InstallRunnerCallback\n---@return InstallHandle\nfunction AbstractPackage:install(opts, callback)\n    error \"Unimplemented.\"\nend\n\n---@param opts? PackageUninstallOpts\n---@param callback? InstallRunnerCallback\n---@return InstallHandle\nfunction AbstractPackage:uninstall(opts, callback)\n    error \"Unimplemented.\"\nend\n\n---@private\n---@param location? InstallLocation\nfunction AbstractPackage:unlink(location)\n    location = location or InstallLocation.global()\n    log.fmt_trace(\"Unlinking\", self, location)\n    local linker = require \"mason-core.installer.linker\"\n    return self:get_receipt(location):ok_or(\"Unable to find receipt.\"):and_then(function(receipt)\n        return linker.unlink(self, receipt, location)\n    end)\nend\n\n---@async\n---@private\n---@return Permit\nfunction AbstractPackage:acquire_permit()\n    error \"Unimplemented.\"\nend\n\nreturn AbstractPackage\n"
  },
  {
    "path": "lua/mason-core/package/init.lua",
    "content": "local AbstractPackage = require \"mason-core.package.AbstractPackage\"\nlocal InstallLocation = require \"mason-core.installer.InstallLocation\"\nlocal InstallRunner = require \"mason-core.installer.InstallRunner\"\nlocal Optional = require \"mason-core.optional\"\nlocal Result = require \"mason-core.result\"\nlocal UninstallRunner = require \"mason-core.installer.UninstallRunner\"\nlocal _ = require \"mason-core.functional\"\nlocal fs = require \"mason-core.fs\"\nlocal path = require \"mason-core.path\"\nlocal platform = require \"mason-core.platform\"\nlocal registry = require \"mason-registry\"\nlocal Semaphore = require(\"mason-core.async.control\").Semaphore\n\n---@class Package : AbstractPackage\n---@field spec RegistryPackageSpec\n---@field local_semaphore Semaphore\nlocal Package = {}\nPackage.__index = Package\nsetmetatable(Package, { __index = AbstractPackage })\n\n---@param package_identifier string\n---@return string, string?\nPackage.Parse = function(package_identifier)\n    local name, version = unpack(vim.split(package_identifier, \"@\"))\n    return name, version\nend\n\n---@alias PackageLanguage string\n\n---@type table<PackageLanguage, PackageLanguage>\nPackage.Lang = setmetatable({}, {\n    __index = function(s, lang)\n        s[lang] = lang\n        return s[lang]\n    end,\n})\n\n---@enum PackageCategory\nPackage.Cat = {\n    Compiler = \"Compiler\",\n    Runtime = \"Runtime\",\n    DAP = \"DAP\",\n    LSP = \"LSP\",\n    Linter = \"Linter\",\n    Formatter = \"Formatter\",\n}\n\n---@alias PackageLicense string\n\n---@type table<PackageLicense, PackageLicense>\nPackage.License = setmetatable({}, {\n    __index = function(s, license)\n        s[license] = license\n        return s[license]\n    end,\n})\n\n---@class RegistryPackageSourceVersionOverride : RegistryPackageSource\n---@field constraint string\n\n---@class RegistryPackageSource\n---@field id string PURL-compliant identifier.\n---@field supported_platforms? Platform[]\n---@field version_overrides? RegistryPackageSourceVersionOverride[]\n\n---@class RegistryPackageSchemas\n---@field lsp string?\n\n---@class RegistryPackageDeprecation\n---@field since string\n---@field message string\n\n---@alias RegistryPackageSpecSchema\n--- | '\"registry+v1\"'\n\n---@class RegistryPackageSpec\n---@field schema RegistryPackageSpecSchema\n---@field name string\n---@field description string\n---@field homepage string\n---@field licenses string[]\n---@field languages string[]\n---@field categories string[]\n---@field deprecation RegistryPackageDeprecation?\n---@field source RegistryPackageSource\n---@field schemas RegistryPackageSchemas?\n---@field bin table<string, string>?\n---@field share table<string, string>?\n---@field opt table<string, string>?\n\n---@param spec RegistryPackageSpec\nlocal function validate_spec(spec)\n    if platform.cached_features[\"nvim-0.11\"] ~= 1 then\n        return\n    end\n    vim.validate(\"schema\", spec.schema, _.equals \"registry+v1\", \"registry+v1\")\n    vim.validate(\"name\", spec.name, \"string\")\n    vim.validate(\"description\", spec.description, \"string\")\n    vim.validate(\"homepage\", spec.homepage, \"string\")\n    vim.validate(\"licenses\", spec.licenses, \"table\")\n    vim.validate(\"categories\", spec.categories, \"table\")\n    vim.validate(\"languages\", spec.languages, \"table\")\n    vim.validate(\"source\", spec.source, \"table\")\n    vim.validate(\"bin\", spec.bin, { \"table\", \"nil\" })\n    vim.validate(\"share\", spec.share, { \"table\", \"nil\" })\nend\n\n---@param spec RegistryPackageSpec\n---@param reg RegistrySource\nfunction Package:new(spec, reg)\n    validate_spec(spec)\n    ---@type Package\n    local instance = AbstractPackage.new(self, spec, reg)\n    instance.local_semaphore = Semaphore:new(1)\n    return instance\nend\n\n---@param opts? PackageInstallOpts\n---@param callback? InstallRunnerCallback\n---@return InstallHandle\nfunction Package:install(opts, callback)\n    opts = opts or {}\n    assert(not self:is_installing(), \"Package is already installing.\")\n    assert(not self:is_uninstalling(), \"Package is uninstalling.\")\n    opts = vim.tbl_extend(\"force\", self.DEFAULT_INSTALL_OPTS, opts or {})\n\n    local handle = self:new_install_handle(opts.location)\n    registry:emit(\"package:install:handle\", handle)\n    local runner = InstallRunner:new(handle, AbstractPackage.SEMAPHORE)\n\n    runner:execute(opts, callback)\n\n    return handle\nend\n\n---@param opts? PackageUninstallOpts\n---@param callback? fun(success: boolean, error: any)\nfunction Package:uninstall(opts, callback)\n    opts = opts or {}\n    assert(self:is_installed(opts.location), \"Package is not installed.\")\n    assert(not self:is_uninstalling(), \"Package is already uninstalling.\")\n    local handle = self:new_uninstall_handle(opts.location)\n    registry:emit(\"package:uninstall:handle\", handle)\n    local runner = UninstallRunner:new(handle, AbstractPackage.SEMAPHORE)\n    runner:execute(opts, callback)\n    return handle\nend\n\n---@param location? InstallLocation\nfunction Package:is_installed(location)\n    location = location or InstallLocation.global()\n    local ok, stat = pcall(vim.loop.fs_stat, location:package(self.name))\n    if not ok or not stat then\n        return false\n    end\n    return stat.type == \"directory\"\nend\n\nfunction Package:get_lsp_settings_schema()\n    local schema_file = InstallLocation.global()\n        :share(path.concat { \"mason-schemas\", \"lsp\", (\"%s.json\"):format(self.name) })\n    if fs.sync.file_exists(schema_file) then\n        return Result.pcall(vim.json.decode, fs.sync.read_file(schema_file), {\n            luanil = { object = true, array = true },\n        }):ok()\n    end\n    return Optional.empty()\nend\n\nfunction Package:get_aliases()\n    return require(\"mason-registry\").get_package_aliases(self.name)\nend\n\n---@async\n---@private\nfunction Package:acquire_permit()\n    return self.local_semaphore:acquire()\nend\n\nfunction Package:__tostring()\n    return (\"Package(name=%s)\"):format(self.name)\nend\n\nreturn Package\n"
  },
  {
    "path": "lua/mason-core/path.lua",
    "content": "local M = {}\n\n---@param path_components string[]\n---@return string\nfunction M.concat(path_components)\n    return vim.fs.normalize(table.concat(path_components, \"/\"))\nend\n\n---@path root_path string\n---@path path string\nfunction M.is_subdirectory(root_path, path)\n    local root_path_normalized = vim.fs.normalize(root_path)\n    local path_normalized = vim.fs.normalize(path)\n    if path_normalized == root_path_normalized then\n        return true\n    end\n    for dir in vim.fs.parents(path_normalized) do\n        if root_path_normalized == dir then\n            return true\n        end\n    end\n    return false\nend\n\nlocal function find_closest_common_parent(from, to)\n    local distance = 0\n    for parent in vim.fs.parents(from) do\n        if to:find(parent, 1, true) then\n            return parent, distance\n        else\n            distance = distance + 1\n        end\n    end\n    return \"/\", distance\nend\n\nfunction M.relative(from, to)\n    local from_normalized = vim.fs.normalize(from)\n    local to_normalized = vim.fs.normalize(to)\n\n    local common_parent, distance = find_closest_common_parent(from_normalized, to_normalized)\n    local relative_path_component = distance == 0 and \".\" or (\"..\"):rep(distance, \"/\")\n    return M.concat { relative_path_component, to_normalized:sub(#common_parent + 1) }\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/pep440/init.lua",
    "content": "local function split_version(version)\n    local parts = {}\n    for part in version:gmatch \"[^.]+\" do\n        table.insert(parts, tonumber(part) or part)\n    end\n    return parts\nend\n\nlocal function compare_versions(version1, version2)\n    local v1_parts = split_version(version1)\n    local v2_parts = split_version(version2)\n    local len = math.max(#v1_parts, #v2_parts)\n\n    for i = 1, len do\n        local v1_part = v1_parts[i] or 0\n        local v2_part = v2_parts[i] or 0\n\n        if v1_part < v2_part then\n            return -1\n        elseif v1_part > v2_part then\n            return 1\n        end\n    end\n\n    return 0\nend\n\nlocal function check_single_specifier(version, specifier)\n    local operator, spec_version = specifier:match \"^([<>=!~]+)%s*(.+)$\"\n    local comp_result = compare_versions(version, spec_version)\n\n    if operator == \"==\" then\n        return comp_result == 0\n    elseif operator == \"!=\" then\n        return comp_result ~= 0\n    elseif operator == \"<=\" then\n        return comp_result <= 0\n    elseif operator == \"<\" then\n        return comp_result < 0\n    elseif operator == \">=\" then\n        return comp_result >= 0\n    elseif operator == \">\" then\n        return comp_result > 0\n    elseif operator == \"~=\" then\n        if comp_result < 0 then\n            return false\n        end\n        local spec_version_components = split_version(spec_version)\n        local version_components = split_version(version)\n        for i = 1, #spec_version_components - 1 do\n            if spec_version_components[i] ~= version_components[i] then\n                return false\n            end\n        end\n        return true\n    else\n        error(\"Unknown operator in version specifier: \" .. operator)\n    end\nend\n\nlocal function check_version(version, specifiers)\n    for specifier in specifiers:gmatch \"[^,]+\" do\n        if not check_single_specifier(version, specifier:match \"^%s*(.-)%s*$\") then\n            return false\n        end\n    end\n    return true\nend\n\nreturn {\n    check_version = check_version,\n}\n"
  },
  {
    "path": "lua/mason-core/platform.lua",
    "content": "local _ = require \"mason-core.functional\"\n\nlocal M = {}\n\nlocal uname = vim.loop.os_uname()\n\n---@alias Platform\n---| '\"darwin_arm64\"'\n---| '\"darwin_x64\"'\n---| '\"linux_arm\"'\n---| '\"linux_arm64\"'\n---| '\"linux_arm64_gnu\"'\n---| '\"linux_arm64_openbsd\"'\n---| '\"linux_arm_gnu\"'\n---| '\"linux_x64\"'\n---| '\"linux_x64_gnu\"'\n---| '\"linux_x64_openbsd\"'\n---| '\"linux_x86\"'\n---| '\"linux_x86_gnu\"'\n---| '\"win_arm\"'\n---| '\"win_arm64\"'\n---| '\"win_x64\"'\n---| '\"win_x86\"'\n\nlocal arch_aliases = {\n    [\"x86_64\"] = \"x64\",\n    [\"i386\"] = \"x86\",\n    [\"i686\"] = \"x86\", -- x86 compat\n    [\"aarch64\"] = \"arm64\",\n    [\"aarch64_be\"] = \"arm64\",\n    [\"armv8b\"] = \"arm64\", -- arm64 compat\n    [\"armv8l\"] = \"arm64\", -- arm64 compat\n}\n\nM.arch = arch_aliases[uname.machine] or uname.machine\nM.sysname = uname.sysname\n\nM.is_headless = #vim.api.nvim_list_uis() == 0\n\nlocal function system(args)\n    if vim.fn.executable(args[1]) == 1 then\n        local ok, output = pcall(vim.fn.system, args)\n        if ok and (vim.v.shell_error == 0 or vim.v.shell_error == 1) then\n            return true, output\n        end\n        return false, output\n    end\n    return false, args[1] .. \" is not executable\"\nend\n\n---@type fun(): ('\"glibc\"' | '\"musl\"')?\nlocal get_libc = _.lazy(function()\n    local getconf_ok, getconf_output = system { \"getconf\", \"GNU_LIBC_VERSION\" }\n    if getconf_ok and getconf_output:find \"glibc\" then\n        return \"glibc\"\n    end\n    local ldd_ok, ldd_output = system { \"ldd\", \"--version\" }\n    if ldd_ok then\n        if ldd_output:find \"musl\" then\n            return \"musl\"\n        elseif ldd_output:find \"GLIBC\" or ldd_output:find \"glibc\" or ldd_output:find \"GNU\" then\n            return \"glibc\"\n        end\n    end\nend)\n\n-- Most of the code that calls into these functions executes outside of the main event loop, where API/fn functions are\n-- disabled. We evaluate these immediately here to avoid issues with main loop synchronization.\nM.cached_features = {\n    [\"win\"] = vim.fn.has \"win32\",\n    [\"win32\"] = vim.fn.has \"win32\",\n    [\"win64\"] = vim.fn.has \"win64\",\n    [\"mac\"] = vim.fn.has \"mac\",\n    [\"darwin\"] = vim.fn.has \"mac\",\n    [\"unix\"] = vim.fn.has \"unix\",\n    [\"linux\"] = vim.fn.has \"linux\",\n    [\"nvim-0.11\"] = vim.fn.has \"nvim-0.11\",\n}\n\n---@type fun(env: string): boolean\nlocal check_env = _.memoize(_.cond {\n    {\n        _.equals \"musl\",\n        function()\n            return get_libc() == \"musl\"\n        end,\n    },\n    {\n        _.equals \"gnu\",\n        function()\n            return get_libc() == \"glibc\"\n        end,\n    },\n    { _.equals \"openbsd\", _.always(uname.sysname == \"OpenBSD\") },\n    { _.T, _.F },\n})\n\n---Table that allows for checking whether the provided targets apply to the current system.\n---Each key is a target tuple consisting of at most 3 targets, in the following order:\n--- 1) OS (e.g. linux, unix, darwin, win) - Mandatory\n--- 2) Architecture (e.g. arm64, x64) - Optional\n--- 3) Environment (e.g. gnu, musl, openbsd) - Optional\n---Each target is separated by a \"_\" character, like so: \"linux_x64_musl\".\n---@type table<string, boolean>\nM.is = setmetatable({}, {\n    __index = function(__, key)\n        local os, arch, env = unpack(vim.split(key, \"_\", { plain = true }))\n        if not M.cached_features[os] or M.cached_features[os] ~= 1 then\n            return false\n        end\n        if arch and arch ~= M.arch then\n            return false\n        end\n        if env and not check_env(env) then\n            return false\n        end\n        return true\n    end,\n})\n\n---@generic T\n---@param platform_table table<Platform, T>\n---@return T\nlocal function get_by_platform(platform_table)\n    if M.is.darwin then\n        return platform_table.darwin or platform_table.mac or platform_table.unix\n    elseif M.is.linux then\n        return platform_table.linux or platform_table.unix\n    elseif M.is.unix then\n        return platform_table.unix\n    elseif M.is.win then\n        return platform_table.win\n    else\n        return nil\n    end\nend\n\nfunction M.when(cases)\n    local case = get_by_platform(cases)\n    if case then\n        return case()\n    else\n        error \"Current platform is not supported.\"\n    end\nend\n\n---@type async fun(): table\nM.os_distribution = _.lazy(function()\n    local parse_os_release = _.compose(_.from_pairs, _.map(_.split \"=\"), _.split \"\\n\")\n\n    ---@param entries table<string, string>\n    local function parse_ubuntu(entries)\n        -- Parses the Ubuntu OS VERSION_ID into their version components, e.g. \"18.04\" -> {major=18, minor=04}\n        local version_id = entries.VERSION_ID:gsub([[\"]], \"\")\n        local version_parts = vim.split(version_id, \"%.\")\n        local major = tonumber(version_parts[1])\n        local minor = tonumber(version_parts[2])\n\n        return {\n            id = \"ubuntu\",\n            version_id = version_id,\n            version = { major = major, minor = minor },\n        }\n    end\n\n    ---@param entries table<string, string>\n    local function parse_centos(entries)\n        -- Parses the CentOS VERSION_ID into a major version (the only thing available).\n        local version_id = entries.VERSION_ID:gsub([[\"]], \"\")\n        local major = tonumber(version_id)\n\n        return {\n            id = \"centos\",\n            version_id = version_id,\n            version = { major = major },\n        }\n    end\n\n    ---Parses the provided contents of an /etc/*-release file and identifies the Linux distribution.\n    local parse_linux_dist = _.cond {\n        { _.prop_eq(\"ID\", \"ubuntu\"), parse_ubuntu },\n        { _.prop_eq(\"ID\", [[\"centos\"]]), parse_centos },\n        { _.T, _.always { id = \"linux-generic\", version = {} } },\n    }\n\n    return M.when {\n        linux = function()\n            local spawn = require \"mason-core.spawn\"\n            return spawn\n                .bash({ \"-c\", \"cat /etc/*-release\" })\n                :map_catching(_.compose(parse_linux_dist, parse_os_release, _.prop \"stdout\"))\n                :recover(function()\n                    return { id = \"linux-generic\", version = {} }\n                end)\n                :get_or_throw()\n        end,\n        darwin = function()\n            return { id = \"macOS\", version = {} }\n        end,\n        win = function()\n            return { id = \"windows\", version = {} }\n        end,\n    }\nend)\n\n---@type async fun(): Result<string>\nM.get_homebrew_prefix = _.lazy(function()\n    assert(M.is.darwin, \"Can only locate Homebrew installation on Mac systems.\")\n    local spawn = require \"mason-core.spawn\"\n    return spawn\n        .brew({ \"--prefix\" })\n        :map_catching(function(result)\n            return vim.trim(result.stdout)\n        end)\n        :map_err(function()\n            return \"Failed to locate Homebrew installation.\"\n        end)\nend)\n\n---@async\nfunction M.get_node_version()\n    local spawn = require \"mason-core.spawn\"\n\n    return spawn.node({ \"--version\" }):map(function(result)\n        -- Parses output such as \"v16.3.1\" into major, minor, patch\n        local _, _, major, minor, patch = _.head(_.split(\"\\n\", result.stdout)):find \"v(%d+)%.(%d+)%.(%d+)\"\n        return { major = tonumber(major), minor = tonumber(minor), patch = tonumber(patch) }\n    end)\nend\n\n-- PATH separator\nM.path_sep = M.is.win and \";\" or \":\"\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/process.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal log = require \"mason-core.log\"\nlocal platform = require \"mason-core.platform\"\nlocal uv = vim.loop\n\n---@alias luv_pipe any\n---@alias luv_handle any\n\n---@class IStdioSink\nlocal IStdioSink = {}\n---@param chunk string\nfunction IStdioSink:stdout(chunk) end\n---@param chunk string\nfunction IStdioSink:stderr(chunk) end\n\n---@class StdioSink : IStdioSink\n---@field stdout_sink? fun(chunk: string)\n---@field stderr_sink? fun(chunk: string)\nlocal StdioSink = {}\nStdioSink.__index = StdioSink\n\n---@param opts { stdout?: fun(chunk: string), stderr?: fun(chunk: string) }\nfunction StdioSink:new(opts)\n    ---@type StdioSink\n    local instance = {}\n    setmetatable(instance, self)\n    instance.stdout_sink = opts.stdout\n    instance.stderr_sink = opts.stderr\n    return instance\nend\n\n---@param chunk string\nfunction StdioSink:stdout(chunk)\n    if self.stdout_sink then\n        self.stdout_sink(chunk)\n    end\nend\n\n---@param chunk string\nfunction StdioSink:stderr(chunk)\n    if self.stderr_sink then\n        self.stderr_sink(chunk)\n    end\nend\n\n---@class BufferedSink : IStdioSink\n---@field buffers { stdout: string[], stderr: string[] }\n---@field events? EventEmitter\nlocal BufferedSink = {}\nBufferedSink.__index = BufferedSink\n\nfunction BufferedSink:new()\n    ---@type BufferedSink\n    local instance = {}\n    setmetatable(instance, self)\n    instance.buffers = {\n        stdout = {},\n        stderr = {},\n    }\n    return instance\nend\n\n---@param events EventEmitter\nfunction BufferedSink:connect_events(events)\n    self.events = events\nend\n\n---@param chunk string\nfunction BufferedSink:stdout(chunk)\n    local stdout = self.buffers.stdout\n    stdout[#stdout + 1] = chunk\n    if self.events then\n        self.events:emit(\"stdout\", chunk)\n    end\nend\n\n---@param chunk string\nfunction BufferedSink:stderr(chunk)\n    local stderr = self.buffers.stderr\n    stderr[#stderr + 1] = chunk\n    if self.events then\n        self.events:emit(\"stderr\", chunk)\n    end\nend\n\nlocal M = {}\n\n---@param pipe luv_pipe\n---@param sink fun(chunk: string)\nlocal function connect_sink(pipe, sink)\n    ---@param err string | nil\n    ---@param data string | nil\n    return function(err, data)\n        if err then\n            log.error(\"Unexpected error when reading pipe.\", err)\n        end\n        if data ~= nil then\n            sink(data)\n        else\n            pipe:read_stop()\n            pipe:close()\n        end\n    end\nend\n\n-- We gather the root env immediately, primarily because of E5560.\n-- Also, there's no particular reason we need to refresh the environment (yet).\nlocal initial_environ = vim.fn.environ()\n\n---@param new_paths string[] A list of paths to prepend the existing PATH with.\nfunction M.extend_path(new_paths)\n    local new_path_str = table.concat(new_paths, platform.path_sep)\n    return (\"%s%s%s\"):format(new_path_str, platform.path_sep, initial_environ.PATH or \"\")\nend\n\n---Merges the provided env param with the user's full environment. Provided env has precedence.\n---@param env table<string, string>\n---@param excluded_var_names string[]|nil\n---@return string[]\nfunction M.graft_env(env, excluded_var_names)\n    local excluded_var_names_set = excluded_var_names and _.set_of(excluded_var_names) or {}\n    local merged_env = {}\n    for key, val in pairs(initial_environ) do\n        if not excluded_var_names_set[key] and env[key] == nil then\n            merged_env[#merged_env + 1] = key .. \"=\" .. val\n        end\n    end\n    for key, val in pairs(env) do\n        if not excluded_var_names_set[key] then\n            merged_env[#merged_env + 1] = key .. \"=\" .. val\n        end\n    end\n    return merged_env\nend\n\n---@param env_list string[]\nlocal function sanitize_env_list(env_list)\n    local sanitized_list = {}\n    for __, env in ipairs(env_list) do\n        local safe_envs = {\n            \"GO111MODULE\",\n            \"GOBIN\",\n            \"GOPATH\",\n            \"PATH\",\n            \"GEM_HOME\",\n            \"GEM_PATH\",\n        }\n        local is_safe_env = _.any(function(safe_env)\n            return env:find(safe_env .. \"=\") == 1\n        end, safe_envs)\n        if is_safe_env then\n            sanitized_list[#sanitized_list + 1] = env\n        else\n            local idx = env:find \"=\"\n            sanitized_list[#sanitized_list + 1] = env:sub(1, idx) .. \"<redacted>\"\n        end\n    end\n    return sanitized_list\nend\n\n---@alias JobSpawnCallback fun(success: boolean, exit_code: integer?, signal: integer?)\n\n---@class JobSpawnOpts\n---@field env string[]? List of \"key=value\" string.\n---@field args string[]\n---@field cwd string\n---@field stdio_sink IStdioSink\n\n---@param cmd string The command/executable.\n---@param opts JobSpawnOpts\n---@param callback JobSpawnCallback\n---@return luv_handle?,luv_pipe[]?,integer? # Returns the job handle and the stdio array on success, otherwise returns nil.\nfunction M.spawn(cmd, opts, callback)\n    local stdin = uv.new_pipe(false)\n    local stdout = uv.new_pipe(false)\n    local stderr = uv.new_pipe(false)\n\n    local stdio = { stdin, stdout, stderr }\n\n    local spawn_opts = {\n        env = opts.env,\n        stdio = stdio,\n        args = opts.args,\n        cwd = opts.cwd,\n        detached = false,\n        hide = true,\n    }\n\n    log.lazy_debug(function()\n        local sanitized_env = opts.env and sanitize_env_list(opts.env) or nil\n        return \"Spawning cmd=%s, spawn_opts=%s\",\n            cmd,\n            {\n                args = opts.args,\n                cwd = opts.cwd,\n                env = sanitized_env,\n            }\n    end)\n\n    local handle, pid_or_err\n    handle, pid_or_err = uv.spawn(cmd, spawn_opts, function(exit_code, signal)\n        local successful = exit_code == 0 and signal == 0\n        handle:close()\n        if not stdin:is_closing() then\n            stdin:close()\n        end\n\n        -- ensure all pipes are closed, for I am a qualified plumber\n        local check = uv.new_check()\n        check:start(function()\n            for i = 1, #stdio do\n                local pipe = stdio[i]\n                if not pipe:is_closing() then\n                    return\n                end\n            end\n            check:stop()\n            check:close()\n            callback(successful, exit_code, signal)\n        end)\n\n        log.fmt_debug(\"Job pid=%s exited with exit_code=%s, signal=%s\", pid_or_err, exit_code, signal)\n    end)\n\n    if handle == nil then\n        log.fmt_error(\"Failed to spawn process. cmd=%s, err=%s\", cmd, pid_or_err)\n        if type(pid_or_err) == \"string\" and pid_or_err:find \"ENOENT\" == 1 then\n            opts.stdio_sink:stderr((\"Could not find executable %q in PATH.\\n\"):format(cmd))\n        else\n            opts.stdio_sink:stderr((\"Failed to spawn process cmd=%s err=%s\\n\"):format(cmd, pid_or_err))\n        end\n        callback(false)\n        return nil, nil, nil\n    end\n\n    log.debug(\"Spawned with pid\", pid_or_err)\n\n    stdout:read_start(connect_sink(stdout, function(...)\n        opts.stdio_sink:stdout(...)\n    end))\n    stderr:read_start(connect_sink(stderr, function(...)\n        opts.stdio_sink:stderr(...)\n    end))\n\n    return handle, stdio, pid_or_err\nend\n\n---@param luv_handle luv_handle\n---@param signal integer\nfunction M.kill(luv_handle, signal)\n    assert(type(signal) == \"number\", \"signal is not a number\")\n    assert(signal > 0 and signal < 32, \"signal must be between 1-31\")\n    log.fmt_trace(\"Sending signal %s to handle %s\", signal, luv_handle)\n    local ok, is_active = pcall(uv.is_active, luv_handle)\n    if not ok or not is_active then\n        log.fmt_trace(\"Tried to send signal %s to inactive uv handle.\", signal)\n        return\n    end\n    uv.process_kill(luv_handle, signal)\nend\n\nM.StdioSink = StdioSink\nM.BufferedSink = BufferedSink\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/providers.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal log = require \"mason-core.log\"\nlocal settings = require \"mason.settings\"\n\n---@alias GitHubRelease { tag_name: string, prerelease: boolean, draft: boolean, assets: table[] }\n---@alias GitHubTag { name: string }\n\n---@class GitHubProvider\n---@field get_latest_release? async fun(repo: string): Result # Result<GitHubRelease>\n---@field get_all_release_versions? async fun(repo: string): Result # Result<string[]>\n---@field get_latest_tag? async fun(repo: string): Result # Result<GitHubTag>\n---@field get_all_tags? async fun(repo: string): Result # Result<string[]>\n\n---@alias NpmPackage { name: string, version: string }\n\n---@class NpmProvider\n---@field get_latest_version? async fun(pkg: string): Result # Result<NpmPackage>\n---@field get_all_versions? async fun(pkg: string): Result # Result<string[]>\n\n---@alias PyPiPackage { name: string, version: string }\n\n---@class PyPiProvider\n---@field get_latest_version? async fun(pkg: string): Result # Result<PyPiPackage>\n---@field get_all_versions? async fun(pkg: string): Result # Result<string[]> # Sorting should not be relied upon due to \"proprietary\" sorting algo in pip that is difficult to replicate in mason-registry-api.\n---@field get_supported_python_versions? async fun(pkg: string, version: string): Result # Result<string> # Returns a version specifier as provided by the PyPI API (see PEP440).\n\n---@alias RubyGem { name: string, version: string }\n\n---@class RubyGemsProvider\n---@field get_latest_version? async fun(gem: string): Result # Result<RubyGem>\n---@field get_all_versions? async fun(gem: string): Result # Result<string[]>\n\n---@alias PackagistPackage { name: string, version: string }\n\n---@class PackagistProvider\n---@field get_latest_version? async fun(pkg: string): Result # Result<PackagistPackage>\n---@field get_all_versions? async fun(pkg: string): Result # Result<string[]>\n\n---@alias Crate { name: string, version: string }\n\n---@class CratesProvider\n---@field get_latest_version? async fun(crate: string): Result # Result<Crate>\n---@field get_all_versions? async fun(crate: string): Result # Result<string[]>\n\n---@class GolangProvider\n---@field get_all_versions? async fun(pkg: string): Result # Result<string[]>\n\n---@class OpenVSXProvider\n---@field get_latest_version? async fun(namespace: string, extension: string): Result # Result<Crate>\n---@field get_all_versions? async fun(namespace: string, extension: string): Result # Result<string[]>\n\n---@class Provider\n---@field github?     GitHubProvider\n---@field npm?        NpmProvider\n---@field pypi?       PyPiProvider\n---@field rubygems?   RubyGemsProvider\n---@field packagist?  PackagistProvider\n---@field crates?     CratesProvider\n---@field golang?     GolangProvider\n---@field openvsx?    OpenVSXProvider\n\nlocal function service_mt(service)\n    return setmetatable({}, {\n        __index = function(_, method)\n            return function(...)\n                if #settings.current.providers < 1 then\n                    log.error \"No providers configured.\"\n                    return Result.failure \"1 or more providers are required.\"\n                end\n                for _, provider_module in ipairs(settings.current.providers) do\n                    local ok, provider = pcall(require, provider_module)\n                    if ok and provider then\n                        local impl = provider[service] and provider[service][method]\n                        if impl then\n                            ---@type boolean, Result\n                            local ok, result = pcall(impl, ...)\n                            if ok and result:is_success() then\n                                return result\n                            else\n                                if getmetatable(result) == Result then\n                                    log.fmt_error(\"Provider %s %s failed: %s\", service, method, result:err_or_nil())\n                                else\n                                    log.fmt_error(\"Provider %s %s errored: %s\", service, method, result)\n                                end\n                            end\n                        end\n                    else\n                        log.fmt_error(\"Unable to find provider %s is not registered. %s\", provider_module, provider)\n                    end\n                end\n                local err = (\"No provider implementation succeeded for %s.%s\"):format(service, method)\n                log.error(err)\n                return Result.failure(err)\n            end\n        end,\n    })\nend\n\n---@type Provider\nlocal providers = setmetatable({}, {\n    __index = function(tbl, service)\n        tbl[service] = service_mt(service)\n        return tbl[service]\n    end,\n})\n\nreturn providers\n"
  },
  {
    "path": "lua/mason-core/purl.lua",
    "content": "local Optional = require \"mason-core.optional\"\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\n\nlocal M = {}\n\n-- Fully spec-compliant parser for purls (https://github.com/package-url/purl-spec)\n\n---@param str string\nlocal function parse_hex(str)\n    return tonumber(str, 16)\nend\n\n---@param char string\nlocal function percent_encode(char)\n    return (\"%%%x\"):format(string.byte(char, 1, 1))\nend\n\nlocal decode_percent_encoding = _.gsub(\"%%([A-Fa-f0-9][A-Fa-f0-9])\", _.compose(string.char, parse_hex))\nlocal encode_percent_encoding = _.gsub(\"[!#$&'%(%)%*%+;=%?@%[%] ]\", percent_encode)\n\nlocal function validate_conan(purl)\n    if purl.namespace and not _.path({ \"qualifiers\", \"channel\" }, purl) then\n        return Result.failure \"Missing channel qualifier.\"\n    elseif not purl.namespace and _.path({ \"qualifiers\", \"channel\" }, purl) then\n        return Result.failure \"Missing namespace.\"\n    end\n    return Result.success(purl)\nend\n\nlocal function validate_cran(purl)\n    if not purl.version then\n        return Result.failure \"Missing version.\"\n    end\n    return Result.success(purl)\nend\n\nlocal function validate_swift(purl)\n    if not purl.namespace then\n        return Result.failure \"Missing namespace.\"\n    end\n    if not purl.version then\n        return Result.failure \"Missing version.\"\n    end\n    return Result.success(purl)\nend\n\n---@class Purl\n---@field scheme '\"pkg\"'\n---@field type string\n---@field namespace string?\n---@field name string\n---@field version string?\n---@field qualifiers table<string, string>?\n---@field subpath string?\n\n---@param str string\nlocal function split_once_right(str, char)\n    for i = #str, 1, -1 do\n        if str:sub(i, i) == char then\n            local segment = str:sub(i + 1, #str)\n            return str:sub(1, i - 1), segment\n        end\n    end\n    return str\nend\n\n---@param str string\nlocal function split_once_left(str, char)\n    for i = 1, #str do\n        if str:sub(i, i) == char then\n            local segment = str:sub(1, i - 1)\n            return segment, str:sub(i + 1)\n        end\n    end\n    return str\nend\n\nlocal function left_trim(char, str)\n    for i = 1, #str do\n        if str:sub(i, i) ~= char then\n            return i\n        end\n    end\n    return #str + 1\nend\n\nlocal function right_trim(char, str)\n    for i = #str, 1, -1 do\n        if str:sub(i, i) ~= char then\n            return i\n        end\n    end\n    return #str + 1\nend\n\n---@param char string\n---@param str string\nlocal function trim(char, str)\n    return str:sub(left_trim(char, str), right_trim(char, str))\nend\n\nlocal parse_subpath = _.compose(\n    _.join \"/\",\n    _.filter_map(function(segment)\n        if segment == \".\" or segment == \"..\" or segment == \"\" then\n            return Optional.empty()\n        end\n        return Optional.of(decode_percent_encoding(segment))\n    end),\n    _.split \"/\",\n    _.partial(trim, \"/\")\n)\n\nlocal parse_qualifiers = _.compose(\n    _.evolve {\n        checksum = _.split \",\",\n    },\n    _.from_pairs,\n    _.filter_map(function(pair)\n        local key, value = split_once_left(pair, \"=\")\n        if value ~= nil and value ~= \"\" then\n            return Optional.of { _.to_lower(key), decode_percent_encoding(value) }\n        else\n            return Optional.empty()\n        end\n    end),\n    _.split \"&\"\n)\n\nlocal parse_namespace = _.compose(\n    _.join \"/\",\n    _.filter_map(function(segment)\n        if segment == \"\" then\n            return Optional.empty()\n        end\n        return Optional.of(decode_percent_encoding(segment))\n    end),\n    _.split \"/\"\n)\n\nlocal pypi = _.evolve {\n    name = _.compose(_.to_lower, _.gsub(\"_\", \"-\")),\n}\n\nlocal huggingface = _.evolve {\n    version = _.to_lower,\n}\n\nlocal azuredatabricks = _.evolve {\n    name = _.to_lower,\n    namespace = _.to_lower,\n}\n\nlocal bitbucket = _.evolve {\n    name = _.to_lower,\n    namespace = _.to_lower,\n}\n\nlocal github = _.evolve {\n    name = _.to_lower,\n    namespace = _.to_lower,\n}\n\nlocal composer = _.evolve {\n    name = _.to_lower,\n    namespace = _.to_lower,\n}\n\nlocal is_mlflow_azuredatabricks = _.all_pass {\n    _.prop_eq(\"type\", \"mlflow\"),\n    _.path_satisfies(_.matches \"^https?://.*azuredatabricks%.net\", { \"qualifiers\", \"repository_url\" }),\n}\n\nlocal type_validations = _.cond {\n    { _.prop_eq(\"type\", \"conan\"), validate_conan },\n    { _.prop_eq(\"type\", \"cran\"), validate_cran },\n    { _.prop_eq(\"type\", \"swift\"), validate_swift },\n    { _.T, Result.success },\n}\n\nlocal type_transforms = _.cond {\n    { _.prop_eq(\"type\", \"bitbucket\"), bitbucket },\n    { _.prop_eq(\"type\", \"composer\"), composer },\n    { _.prop_eq(\"type\", \"github\"), github },\n    { _.prop_eq(\"type\", \"pypi\"), pypi },\n    { _.prop_eq(\"type\", \"huggingface\"), huggingface },\n    { is_mlflow_azuredatabricks, azuredatabricks },\n    { _.T, _.identity },\n}\n\nlocal type_specific_transforms = _.compose(type_validations, type_transforms)\n\n---@param raw_purl string\n---@return Result # Result<Purl>\nfunction M.parse(raw_purl)\n    -- Implementation of recommended parsing algo\n    -- https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#how-to-parse-a-purl-string-in-its-components\n    local remainder, subpath = split_once_right(raw_purl, \"#\")\n    if subpath then\n        subpath = parse_subpath(subpath)\n    end\n\n    local remainder, qualifiers = split_once_right(remainder, \"?\")\n    if qualifiers then\n        qualifiers = parse_qualifiers(qualifiers)\n        if not _.all(_.matches \"^[a-zA-Z%-_%.][0-9a-zA-Z%-_%.]*$\", _.keys(qualifiers)) then\n            return Result.failure \"Malformed purl (invalid qualifier names).\"\n        end\n    end\n\n    local scheme, remainder = split_once_left(remainder, \":\")\n    if not remainder then\n        return Result.failure \"Malformed purl (missing type, namespace, name, version components).\"\n    end\n    if scheme ~= \"pkg\" then\n        return Result.failure \"Malformed purl (invalid scheme).\"\n    end\n    remainder = trim(\"/\", remainder)\n\n    local type, remainder = split_once_left(remainder, \"/\")\n    if not remainder then\n        return Result.failure \"Malformed purl (missing namespace, name, version components)\"\n    end\n    type = _.to_lower(type)\n\n    local remainder, version = split_once_right(remainder, \"@\")\n    if version then\n        version = decode_percent_encoding(version)\n    end\n\n    local remainder, name = split_once_right(remainder, \"/\")\n    if not name then\n        name = remainder\n        remainder = nil\n    end\n    if name == \"\" then\n        return Result.failure \"Malformed purl (missing name).\"\n    end\n    name = decode_percent_encoding(name)\n\n    local namespace = remainder\n    if namespace then\n        namespace = parse_namespace(namespace)\n    end\n\n    return type_specific_transforms {\n        scheme = scheme,\n        type = type,\n        namespace = namespace,\n        name = name,\n        version = version,\n        qualifiers = qualifiers,\n        subpath = subpath,\n    }\nend\n\nlocal stringify_qualifiers = _.compose(\n    _.join \"&\",\n    _.sort_by(_.identity),\n    _.map(_.compose(_.join \"=\", _.evolve { _.identity, encode_percent_encoding })),\n    _.to_pairs,\n    _.evolve {\n        checksum = _.join \",\",\n    }\n)\n\n---@param purl Purl\n---@return string\nfunction M.compile(purl)\n    local str = \"pkg:\"\n    str = str .. purl.type .. \"/\"\n    if purl.namespace then\n        str = str .. encode_percent_encoding(purl.namespace) .. \"/\"\n    end\n    str = str .. purl.name\n    if purl.version then\n        str = str .. \"@\" .. encode_percent_encoding(purl.version)\n    end\n    if purl.qualifiers then\n        str = str .. \"?\" .. stringify_qualifiers(purl.qualifiers)\n    end\n    if purl.subpath then\n        str = str .. \"#\" .. purl.subpath\n    end\n    return str\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/receipt.lua",
    "content": "local Optional = require \"mason-core.optional\"\nlocal Purl = require \"mason-core.purl\"\nlocal _ = require \"mason-core.functional\"\n\nlocal M = {}\n\n---@alias InstallReceiptSchemaVersion\n---| '\"1.0\"'\n---| '\"1.1\"'\n---| '\"2.0\"'\n\n---@alias InstallReceiptSource {type: RegistryPackageSpecSchema, id: string, raw: RegistryPackageSource}\n\n---@class InstallReceiptLinks\n---@field bin? table<string, string>\n---@field share? table<string, string>\n---@field opt? table<string, string>\n\n---@alias InstallReceiptRegistry { proto: '\"github\"' | '\"lua\"' | '\"file\"' }\n\n---@class InstallReceipt\n---@field name string\n---@field schema_version InstallReceiptSchemaVersion\n---@field metrics {start_time:integer, completion_time:integer}\n---@field source InstallReceiptSource\n---@field links InstallReceiptLinks\n---@field install_options PackageInstallOpts\n---@field registry InstallReceiptRegistry\nlocal InstallReceipt = {}\nInstallReceipt.__index = InstallReceipt\n\nfunction InstallReceipt:new(data)\n    return setmetatable(data, self)\nend\n\nfunction InstallReceipt.from_json(json)\n    return InstallReceipt:new(json)\nend\n\nfunction InstallReceipt:__tostring()\n    return (\"InstallReceipt(name=%s, purl=%s)\"):format(self.name, self:get_source().id or \"N/A\")\nend\n\nfunction InstallReceipt:get_name()\n    return self.name\nend\n\n---@return string?\nfunction InstallReceipt:get_installed_package_version()\n    local source = self:get_source()\n    if source.id then\n        return Purl.parse(source.id):map(_.prop \"version\"):get_or_nil()\n    end\nend\n\nfunction InstallReceipt:get_schema_version()\n    return self.schema_version\nend\n\n---@param version string\nfunction InstallReceipt:is_schema_min(version)\n    local semver = require \"mason-vendor.semver\"\n    return semver(self.schema_version) >= semver(version)\nend\n\n---@return InstallReceiptSource\nfunction InstallReceipt:get_source()\n    if self:is_schema_min \"2.0\" then\n        return self.source\n    end\n    return self.primary_source --[[@as InstallReceiptSource]]\nend\n\n---@return string?\nfunction InstallReceipt:get_installed_purl()\n    local source = self:get_source()\n    return source.id\nend\n\nfunction InstallReceipt:get_raw_source()\n    if self:is_schema_min \"2.0\" then\n        return self.source.raw\n    else\n        return nil\n    end\nend\n\nfunction InstallReceipt:get_registry()\n    return self.registry\nend\n\nfunction InstallReceipt:get_install_options()\n    return self.install_options\nend\n\nfunction InstallReceipt:get_links()\n    return self.links\nend\n\nfunction InstallReceipt:to_json()\n    return vim.json.encode(self)\nend\n\n---@class InstallReceiptBuilder\n---@field links InstallReceiptLinks\nlocal InstallReceiptBuilder = {}\nInstallReceiptBuilder.__index = InstallReceiptBuilder\n\nfunction InstallReceiptBuilder:new()\n    ---@type InstallReceiptBuilder\n    local instance = {}\n    setmetatable(instance, self)\n    instance.links = {\n        bin = vim.empty_dict(),\n        share = vim.empty_dict(),\n        opt = vim.empty_dict(),\n    }\n    return instance\nend\n\n---@param name string\nfunction InstallReceiptBuilder:with_name(name)\n    self.name = name\n    return self\nend\n\n---@param source InstallReceiptSource\nfunction InstallReceiptBuilder:with_source(source)\n    self.source = source\n    return self\nend\n\n---@param install_options PackageInstallOpts\nfunction InstallReceiptBuilder:with_install_options(install_options)\n    self.install_options = install_options\n    return self\nend\n\n---@param typ '\"bin\"' | '\"share\"' | '\"opt\"'\n---@param name string\n---@param rel_path string\nfunction InstallReceiptBuilder:with_link(typ, name, rel_path)\n    assert(not self.links[typ][name], (\"%s/%s has already been linked.\"):format(typ, name))\n    self.links[typ][name] = rel_path\n    return self\nend\n\n---@param seconds integer\n---@param microseconds integer\nlocal function to_ms(seconds, microseconds)\n    return (seconds * 1000) + math.floor(microseconds / 1000)\nend\n\n---vim.loop.gettimeofday()\n---@param seconds integer\n---@param microseconds integer\nfunction InstallReceiptBuilder:with_completion_time(seconds, microseconds)\n    self.completion_time = to_ms(seconds, microseconds)\n    return self\nend\n\n---vim.loop.gettimeofday()\n---@param seconds integer\n---@param microseconds integer\nfunction InstallReceiptBuilder:with_start_time(seconds, microseconds)\n    self.start_time = to_ms(seconds, microseconds)\n    return self\nend\n\n---@param registry InstallReceiptRegistry\nfunction InstallReceiptBuilder:with_registry(registry)\n    self.registry = registry\n    return self\nend\n\nfunction InstallReceiptBuilder:build()\n    assert(self.name, \"name is required\")\n    assert(self.start_time, \"start_time is required\")\n    assert(self.completion_time, \"completion_time is required\")\n    assert(self.source, \"source is required\")\n    assert(self.install_options, \"install_options is required\")\n    assert(self.registry, \"registry is required\")\n    return InstallReceipt:new {\n        name = self.name,\n        schema_version = \"2.0\",\n        metrics = {\n            start_time = self.start_time,\n            completion_time = self.completion_time,\n        },\n        install_options = self.install_options,\n        source = self.source,\n        registry = self.registry,\n        links = self.links,\n    }\nend\n\nM.InstallReceiptBuilder = InstallReceiptBuilder\nM.InstallReceipt = InstallReceipt\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/result.lua",
    "content": "---@class Failure\n---@field error any\nlocal Failure = {}\nFailure.__index = Failure\n\nfunction Failure:new(error)\n    ---@type Failure\n    local instance = {}\n    setmetatable(instance, self)\n    instance.error = error\n    return instance\nend\n\n---@class Result\n---@field value any\nlocal Result = {}\nResult.__index = Result\n\nfunction Result:new(value)\n    ---@type Result\n    local instance = {}\n    setmetatable(instance, self)\n    instance.value = value\n    return instance\nend\n\nfunction Result.success(value)\n    return Result:new(value)\nend\n\nfunction Result.failure(error)\n    return Result:new(Failure:new(error))\nend\n\nfunction Result:get_or_nil()\n    if self:is_success() then\n        return self.value\n    end\nend\n\nfunction Result:get_or_else(value)\n    if self:is_success() then\n        return self.value\n    else\n        return value\n    end\nend\n\n---@param exception any? The exception to throw if the result is a failure.\nfunction Result:get_or_throw(exception)\n    if self:is_success() then\n        return self.value\n    else\n        if exception ~= nil then\n            error(exception, 2)\n        else\n            error(self.value.error, 2)\n        end\n    end\nend\n\nfunction Result:err_or_nil()\n    if self:is_failure() then\n        return self.value.error\n    end\nend\n\nfunction Result:is_failure()\n    return getmetatable(self.value) == Failure\nend\n\nfunction Result:is_success()\n    return getmetatable(self.value) ~= Failure\nend\n\n---@param mapper_fn fun(value: any): any\nfunction Result:map(mapper_fn)\n    if self:is_success() then\n        return Result.success(mapper_fn(self.value))\n    else\n        return self\n    end\nend\n\n---@param mapper_fn fun(value: any): any\nfunction Result:map_err(mapper_fn)\n    if self:is_failure() then\n        return Result.failure(mapper_fn(self.value.error))\n    else\n        return self\n    end\nend\n\n---@param mapper_fn fun(value: any): any\nfunction Result:map_catching(mapper_fn)\n    if self:is_success() then\n        local ok, result = pcall(mapper_fn, self.value)\n        if ok then\n            return Result.success(result)\n        else\n            return Result.failure(result)\n        end\n    else\n        return self\n    end\nend\n\n---@param recover_fn fun(value: any): any\nfunction Result:recover(recover_fn)\n    if self:is_failure() then\n        return Result.success(recover_fn(self:err_or_nil()))\n    else\n        return self\n    end\nend\n\n---@param recover_fn fun(value: any): any\nfunction Result:recover_catching(recover_fn)\n    if self:is_failure() then\n        local ok, value = pcall(recover_fn, self:err_or_nil())\n        if ok then\n            return Result.success(value)\n        else\n            return Result.failure(value)\n        end\n    else\n        return self\n    end\nend\n\n---@param fn fun(value: any): any\nfunction Result:on_failure(fn)\n    if self:is_failure() then\n        fn(self.value.error)\n    end\n    return self\nend\n\n---@param fn fun(value: any): any\nfunction Result:on_success(fn)\n    if self:is_success() then\n        fn(self.value)\n    end\n    return self\nend\n\nfunction Result:ok()\n    local Optional = require \"mason-core.optional\"\n    if self:is_success() then\n        return Optional.of(self.value)\n    else\n        return Optional.empty()\n    end\nend\n\n---@param fn fun(value: any): Result\nfunction Result:and_then(fn)\n    if self:is_success() then\n        return fn(self.value)\n    else\n        return self\n    end\nend\n\n---@param fn fun(err: any): Result\nfunction Result:or_else(fn)\n    if self:is_failure() then\n        return fn(self:err_or_nil())\n    else\n        return self\n    end\nend\n\n---@param fn fun(): any\n---@return Result\nfunction Result.run_catching(fn)\n    local ok, result = pcall(fn)\n    if ok then\n        return Result.success(result)\n    else\n        return Result.failure(result)\n    end\nend\n\n---@generic T\n---@param fn fun(try: fun(result: Result)): T?\n---@return Result # Result<T>\nfunction Result.try(fn)\n    local thread = coroutine.create(fn)\n    local step\n    step = function(...)\n        local ok, result = coroutine.resume(thread, ...)\n        if not ok then\n            return Result.failure(result)\n        end\n        if coroutine.status(thread) == \"dead\" then\n            if getmetatable(result) == Result then\n                return result\n            else\n                return Result.success(result)\n            end\n        elseif getmetatable(result) == Result then\n            if result:is_failure() then\n                return result\n            else\n                return step(result:get_or_nil())\n            end\n        else\n            -- yield to parent coroutine\n            return step(coroutine.yield(result))\n        end\n    end\n    return step(coroutine.yield)\nend\n\nfunction Result.pcall(fn, ...)\n    local ok, res = pcall(fn, ...)\n    if ok then\n        return Result.success(res)\n    else\n        return Result.failure(res)\n    end\nend\n\nreturn Result\n"
  },
  {
    "path": "lua/mason-core/semver.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal semver = require \"mason-vendor.semver\"\n\nlocal M = {}\n\n---@param version string\n---@return Semver\nfunction M.new(version)\n    version = version:gsub(\"^v\", \"\")\n    return semver(version)\nend\n\n---@param version string\n---@return Result # Result<Semver>\nfunction M.parse(version)\n    return Result.pcall(M.new, version)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/spawn.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal log = require \"mason-core.log\"\nlocal platform = require \"mason-core.platform\"\nlocal process = require \"mason-core.process\"\n\nlocal is_not_nil = _.complement(_.equals(vim.NIL))\n\n---@alias JobSpawn table<string, async fun(opts: SpawnArgs): Result>\n---@type JobSpawn\nlocal spawn = {\n    _flatten_cmd_args = _.compose(_.filter(is_not_nil), _.flatten),\n}\n\n---@param cmd string\n---@param path? string\nlocal function exepath(cmd, path)\n    local function get_exepath(cmd)\n        if path then\n            local old_path = vim.env.PATH\n            vim.env.PATH = path\n            local expanded_cmd = vim.fn.exepath(cmd)\n            vim.env.PATH = old_path\n            return expanded_cmd\n        else\n            return vim.fn.exepath(cmd)\n        end\n    end\n\n    if platform.is.win then\n        -- On Windows, exepath() assumes the system is capable of executing \"Unix-like\" executables if the shell is a Unix\n        -- shell. We temporarily override it to a Windows shell (\"powershell\") to avoid that behaviour.\n        local old_shell = vim.o.shell\n        vim.o.shell = \"powershell\"\n        local expanded_cmd = get_exepath(cmd)\n        vim.o.shell = old_shell\n        return expanded_cmd\n    else\n        return get_exepath(cmd)\n    end\nend\n\nlocal function Failure(err, cmd)\n    return Result.failure(setmetatable(err, {\n        __tostring = function()\n            return (\"spawn: %s failed with exit code %s and signal %s. %s\"):format(\n                cmd,\n                err.exit_code or \"-\",\n                err.signal or \"-\",\n                err.stderr or \"\"\n            )\n        end,\n    }))\nend\n\nlocal get_path_from_env_list = _.compose(_.strip_prefix \"PATH=\", _.find_first(_.starts_with \"PATH=\"))\n\n---@class SpawnArgs\n---@field with_paths string[]? Paths to add to the PATH environment variable.\n---@field env table<string, string>? Example { SOME_ENV = \"value\", SOME_OTHER_ENV = \"some_value\" }\n---@field env_raw string[]? Example: { \"SOME_ENV=value\", \"SOME_OTHER_ENV=some_value\" }\n---@field stdio_sink StdioSink? If provided, will be used to write to stdout and stderr.\n---@field cwd string?\n---@field on_spawn (fun(handle: luv_handle, stdio: luv_pipe[], pid: integer))? Will be called when the process successfully spawns.\n\nsetmetatable(spawn, {\n    ---@param canonical_cmd string\n    __index = function(self, canonical_cmd)\n        ---@param args SpawnArgs\n        return function(args)\n            local cmd_args = self._flatten_cmd_args(args)\n            local env = args.env\n\n            if args.with_paths then\n                env = env or {}\n                env.PATH = process.extend_path(args.with_paths)\n            end\n\n            ---@type JobSpawnOpts\n            local spawn_args = {\n                stdio_sink = args.stdio_sink,\n                cwd = args.cwd,\n                env = env and process.graft_env(env) or args.env_raw,\n                args = cmd_args,\n            }\n\n            if not spawn_args.stdio_sink then\n                spawn_args.stdio_sink = process.BufferedSink:new()\n            end\n\n            local cmd = canonical_cmd\n\n            -- Find the executable path via vim.fn.exepath on Windows because libuv fails to resolve certain executables\n            -- in PATH.\n            if platform.is.win then\n                a.scheduler()\n                local expanded_cmd = exepath(canonical_cmd, spawn_args.env and get_path_from_env_list(spawn_args.env))\n                if expanded_cmd ~= \"\" then\n                    cmd = expanded_cmd\n                end\n            end\n\n            local _, exit_code, signal = a.wait(function(resolve)\n                local handle, stdio, pid = process.spawn(cmd, spawn_args, resolve)\n                if args.on_spawn and handle and stdio and pid then\n                    args.on_spawn(handle, stdio, pid)\n                end\n            end)\n\n            if exit_code == 0 and signal == 0 then\n                if getmetatable(spawn_args.stdio_sink) == process.BufferedSink then\n                    local sink = spawn_args.stdio_sink --[[@as BufferedSink]]\n                    return Result.success {\n                        stdout = table.concat(sink.buffers.stdout, \"\") or nil,\n                        stderr = table.concat(sink.buffers.stderr, \"\") or nil,\n                    }\n                else\n                    return Result.success()\n                end\n            else\n                if getmetatable(spawn_args.stdio_sink) == process.BufferedSink then\n                    local sink = spawn_args.stdio_sink --[[@as BufferedSink]]\n                    return Failure({\n                        exit_code = exit_code,\n                        signal = signal,\n                        stdout = table.concat(sink.buffers.stdout, \"\") or nil,\n                        stderr = table.concat(sink.buffers.stderr, \"\") or nil,\n                    }, canonical_cmd)\n                else\n                    return Failure({\n                        exit_code = exit_code,\n                        signal = signal,\n                    }, canonical_cmd)\n                end\n            end\n        end\n    end,\n})\n\nreturn spawn\n"
  },
  {
    "path": "lua/mason-core/terminator.lua",
    "content": "local a = require \"mason-core.async\"\n\n-- Hasta la vista, baby.\n--                      ______\n--                    <((((((\\\\\\\n--                    /      . }\\\n--                    ;--..--._|}\n-- (\\                 '--/\\--'  )\n--  \\\\                | '-'  :'|\n--   \\\\               . -==- .-|\n--    \\\\               \\.__.'   \\--._\n--    [\\\\          __.--|       //  _/'--.\n--    \\ \\\\       .'-._ ('-----'/ __/      \\\n--     \\ \\\\     /   __>|      | '--.       |\n--      \\ \\\\   |   \\   |     /    /       /\n--       \\ '\\ /     \\  |     |  _/       /\n--        \\  \\       \\ |     | /        /\n--  snd    \\  \\      \\        /\n\nlocal M = {}\n\n---@async\n---@param handles InstallHandle[]\n---@param grace_ms integer\nlocal function terminate_handles(handles, grace_ms)\n    a.wait_all(vim.tbl_map(\n        ---@param handle InstallHandle\n        function(handle)\n            return function()\n                local timer\n                if not handle:is_closed() then\n                    handle:terminate()\n                    timer = vim.defer_fn(function()\n                        if not handle:is_closed() then\n                            handle:kill(9) -- SIGKILL\n                        end\n                    end, grace_ms)\n                end\n                a.wait(function(resolve)\n                    if handle:is_closed() then\n                        resolve()\n                    else\n                        handle:once(\"closed\", resolve)\n                    end\n                end)\n                if timer then\n                    timer:stop()\n                end\n            end\n        end,\n        handles\n    ))\nend\n\nlocal active_handles = {}\n\n---@param handle InstallHandle\nfunction M.register(handle)\n    if handle:is_closed() then\n        return\n    end\n    active_handles[handle] = true\n    handle:once(\"closed\", function()\n        active_handles[handle] = nil\n    end)\nend\n\n---@param grace_ms integer\nfunction M.terminate(grace_ms)\n    local handles = vim.tbl_keys(active_handles)\n    if #handles > 0 then\n        local package_names = vim.tbl_map(function(h)\n            return h.package.name\n        end, handles)\n        table.sort(package_names)\n\n        -- 1. Print warning message.\n        vim.api.nvim_echo({\n            {\n                \"[mason.nvim] Neovim is exiting while packages are still installing. Terminating all installations…\",\n                \"WarningMsg\",\n            },\n        }, true, {})\n        vim.cmd \"redraw\"\n\n        -- 2. Synchronously terminate all installation handles.\n        a.run_blocking(function()\n            terminate_handles(handles, grace_ms)\n        end)\n\n        -- 3. Schedule error message to be displayed so that Neovim prints it to the tty.\n        --    XXX: does this need to be conditional on which UIs are attached?\n        vim.schedule(function()\n            vim.api.nvim_err_writeln(\n                (\"[mason.nvim] Neovim exited while the following packages were installing. Installation was aborted.\\n- %s\"):format(\n                    table.concat(package_names, #package_names > 5 and \", \" or \"\\n- \")\n                )\n            )\n        end)\n    end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/ui/display.lua",
    "content": "local EventEmitter = require \"mason-core.EventEmitter\"\nlocal log = require \"mason-core.log\"\nlocal settings = require \"mason.settings\"\nlocal state = require \"mason-core.ui.state\"\n\nlocal M = {}\n\n---@generic T\n---@param debounced_fn fun(arg1: T)\n---@return fun(arg1: T)\nlocal function debounced(debounced_fn)\n    local queued = false\n    local last_arg = nil\n    return function(a)\n        last_arg = a\n        if queued then\n            return\n        end\n        queued = true\n        vim.schedule(function()\n            debounced_fn(last_arg)\n            queued = false\n            last_arg = nil\n        end)\n    end\nend\n\n---@param line string\n---@param render_context RenderContext\nlocal function get_styles(line, render_context)\n    local indentation = 0\n\n    for i = 1, #render_context.applied_block_styles do\n        local styles = render_context.applied_block_styles[i]\n        for j = 1, #styles do\n            local style = styles[j]\n            if style == \"INDENT\" then\n                indentation = indentation + 2\n            elseif style == \"CENTERED\" then\n                local padding = math.floor((render_context.viewport_context.win_width - #line) / 2)\n                indentation = math.max(0, padding) -- CENTERED overrides any already applied indentation\n            end\n        end\n    end\n\n    return {\n        indentation = indentation,\n    }\nend\n\n---@param viewport_context ViewportContext\n---@param node INode\n---@param _render_context RenderContext?\n---@param _output RenderOutput?\nlocal function render_node(viewport_context, node, _render_context, _output)\n    ---@class RenderContext\n    ---@field viewport_context ViewportContext\n    ---@field applied_block_styles CascadingStyle[]\n    local render_context = _render_context\n        or {\n            viewport_context = viewport_context,\n            applied_block_styles = {},\n        }\n    ---@class RenderHighlight\n    ---@field hl_group string\n    ---@field line number\n    ---@field col_start number\n    ---@field col_end number\n\n    ---@class RenderKeybind\n    ---@field line number\n    ---@field key string\n    ---@field effect string\n    ---@field payload any\n\n    ---@class RenderDiagnostic\n    ---@field line number\n    ---@field diagnostic {message: string, severity: integer, source: string|nil}\n\n    ---@class RenderOutput\n    ---@field lines string[]: The buffer lines.\n    ---@field virt_texts {line: integer, content: table}[]: List of tuples.\n    ---@field highlights RenderHighlight[]\n    ---@field keybinds RenderKeybind[]\n    ---@field diagnostics RenderDiagnostic[]\n    ---@field sticky_cursors { line_map: table<number, string>, id_map: table<string, number> }\n    local output = _output\n        or {\n            lines = {},\n            virt_texts = {},\n            highlights = {},\n            keybinds = {},\n            diagnostics = {},\n            sticky_cursors = { line_map = {}, id_map = {} },\n        }\n\n    if node.type == \"VIRTUAL_TEXT\" then\n        output.virt_texts[#output.virt_texts + 1] = {\n            line = #output.lines - 1,\n            content = node.virt_text,\n        }\n    elseif node.type == \"HL_TEXT\" then\n        for i = 1, #node.lines do\n            local line = node.lines[i]\n            local line_highlights = {}\n            local full_line = \"\"\n            for j = 1, #line do\n                local span = line[j]\n                local content, hl_group = span[1], span[2]\n                local col_start = #full_line\n                full_line = full_line .. content\n                if hl_group ~= \"\" then\n                    line_highlights[#line_highlights + 1] = {\n                        hl_group = hl_group,\n                        line = #output.lines,\n                        col_start = col_start,\n                        col_end = col_start + #content,\n                    }\n                end\n            end\n\n            -- only apply cascading styles to non-empty lines\n            if string.len(full_line) > 0 then\n                local active_styles = get_styles(full_line, render_context)\n                full_line = (\" \"):rep(active_styles.indentation) .. full_line\n                for j = 1, #line_highlights do\n                    local highlight = line_highlights[j]\n                    highlight.col_start = highlight.col_start + active_styles.indentation\n                    highlight.col_end = highlight.col_end + active_styles.indentation\n                    output.highlights[#output.highlights + 1] = highlight\n                end\n            end\n\n            output.lines[#output.lines + 1] = full_line\n        end\n    elseif node.type == \"NODE\" or node.type == \"CASCADING_STYLE\" then\n        if node.type == \"CASCADING_STYLE\" then\n            render_context.applied_block_styles[#render_context.applied_block_styles + 1] = node.styles\n        end\n        for i = 1, #node.children do\n            render_node(viewport_context, node.children[i], render_context, output)\n        end\n        if node.type == \"CASCADING_STYLE\" then\n            render_context.applied_block_styles[#render_context.applied_block_styles] = nil\n        end\n    elseif node.type == \"KEYBIND_HANDLER\" then\n        output.keybinds[#output.keybinds + 1] = {\n            line = node.is_global and -1 or #output.lines,\n            key = node.key,\n            effect = node.effect,\n            payload = node.payload,\n        }\n    elseif node.type == \"DIAGNOSTICS\" then\n        output.diagnostics[#output.diagnostics + 1] = {\n            line = #output.lines,\n            message = node.diagnostic.message,\n            severity = node.diagnostic.severity,\n            source = node.diagnostic.source,\n        }\n    elseif node.type == \"STICKY_CURSOR\" then\n        output.sticky_cursors.id_map[node.id] = #output.lines\n        output.sticky_cursors.line_map[#output.lines] = node.id\n    end\n\n    return output\nend\n\n-- exported for tests\nM._render_node = render_node\n\n---@alias WindowOpts { effects?: table<string, fun()>, winhighlight?: string[], border?: string|table }\n\n---@param size number\n---@param viewport integer\nlocal function calc_size(size, viewport)\n    return size > 1 and math.min(size, viewport) or math.floor(size * viewport)\nend\n\n---@param opts WindowOpts\n---@param sizes_only boolean Whether to only return properties that control the window size.\nlocal function create_popup_window_opts(opts, sizes_only)\n    local lines = vim.o.lines - vim.o.cmdheight\n    local columns = vim.o.columns\n    local height = calc_size(settings.current.ui.height, lines)\n    local width = calc_size(settings.current.ui.width, columns)\n    local row = math.floor((lines - height) / 2)\n    local col = math.floor((columns - width) / 2)\n    if opts.border ~= \"none\" and opts.border ~= \"\" then\n        row = math.max(row - 1, 0)\n        col = math.max(col - 1, 0)\n    end\n\n    local popup_layout = {\n        height = height,\n        width = width,\n        row = row,\n        col = col,\n        relative = \"editor\",\n        style = \"minimal\",\n        zindex = 45,\n    }\n\n    if not sizes_only then\n        popup_layout.border = opts.border\n    end\n\n    return popup_layout\nend\n\nlocal function create_backdrop_window_opts()\n    return {\n        relative = \"editor\",\n        width = vim.o.columns,\n        height = vim.o.lines,\n        row = 0,\n        col = 0,\n        style = \"minimal\",\n        focusable = false,\n        border = \"none\",\n        zindex = 44,\n    }\nend\n\n---@param name string Human readable identifier.\n---@param filetype string\nfunction M.new_view_only_win(name, filetype)\n    local namespace = vim.api.nvim_create_namespace((\"installer_%s\"):format(name))\n    local bufnr, backdrop_bufnr, renderer, mutate_state, get_state, unsubscribe, win_id, backdrop_win_id, window_mgmt_augroup, autoclose_augroup, registered_keymaps, registered_keybinds, registered_effect_handlers, sticky_cursor\n    local has_initiated = false\n    ---@type WindowOpts\n    local window_opts = {}\n\n    local events = EventEmitter:new()\n\n    vim.diagnostic.config({\n        virtual_text = {\n            severity = { min = vim.diagnostic.severity.HINT, max = vim.diagnostic.severity.ERROR },\n        },\n        right_align = false,\n        underline = false,\n        signs = false,\n        virtual_lines = false,\n    }, namespace)\n\n    local function close_window()\n        -- We queue the win_buf to be deleted in a schedule call, otherwise when used with folke/which-key (and\n        -- set timeoutlen=0) we run into a weird segfault.\n        -- It should probably be unnecessary once https://github.com/neovim/neovim/issues/15548 is resolved\n        vim.schedule(function()\n            if win_id and vim.api.nvim_win_is_valid(win_id) then\n                log.trace \"Deleting window\"\n                vim.api.nvim_win_close(win_id, true)\n            end\n        end)\n    end\n\n    ---@param line number\n    ---@param key string\n    local function call_effect_handler(line, key)\n        local line_keybinds = registered_keybinds[line]\n        if line_keybinds then\n            local keybind = line_keybinds[key]\n            if keybind then\n                local effect_handler = registered_effect_handlers[keybind.effect]\n                if effect_handler then\n                    log.fmt_trace(\"Calling handler for effect %s on line %d for key %s\", keybind.effect, line, key)\n                    effect_handler { payload = keybind.payload }\n                    return true\n                end\n            end\n        end\n        return false\n    end\n\n    local function dispatch_effect(key)\n        local line = vim.api.nvim_win_get_cursor(0)[1]\n        log.fmt_trace(\"Dispatching effect on line %d, key %s, bufnr %s\", line, key, bufnr)\n        call_effect_handler(line, key) -- line keybinds\n        call_effect_handler(-1, key) -- global keybinds\n    end\n\n    local output\n    local draw = function(view)\n        local win_valid = win_id ~= nil and vim.api.nvim_win_is_valid(win_id)\n        local buf_valid = bufnr ~= nil and vim.api.nvim_buf_is_valid(bufnr)\n\n        if not win_valid or not buf_valid then\n            -- the window has been closed or the buffer is somehow no longer valid\n            unsubscribe(true)\n            log.trace(\"Buffer or window is no longer valid\", win_id, bufnr)\n            return\n        end\n\n        local win_width = vim.api.nvim_win_get_width(win_id)\n        ---@class ViewportContext\n        local viewport_context = {\n            win_width = win_width,\n        }\n        local cursor_pos_pre_render = vim.api.nvim_win_get_cursor(win_id)\n        if output then\n            sticky_cursor = output.sticky_cursors.line_map[cursor_pos_pre_render[1]]\n        end\n\n        output = render_node(viewport_context, view)\n        local lines, virt_texts, highlights, keybinds, diagnostics =\n            output.lines, output.virt_texts, output.highlights, output.keybinds, output.diagnostics\n\n        -- set line contents\n        vim.api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1)\n        vim.api.nvim_buf_set_option(bufnr, \"modifiable\", true)\n        vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)\n        vim.api.nvim_buf_set_option(bufnr, \"modifiable\", false)\n\n        -- restore sticky cursor position\n        if sticky_cursor then\n            local new_sticky_cursor_line = output.sticky_cursors.id_map[sticky_cursor]\n            if new_sticky_cursor_line and new_sticky_cursor_line ~= cursor_pos_pre_render then\n                vim.api.nvim_win_set_cursor(win_id, { new_sticky_cursor_line, cursor_pos_pre_render[2] })\n            end\n        end\n\n        -- set virtual texts\n        for i = 1, #virt_texts do\n            local virt_text = virt_texts[i]\n            vim.api.nvim_buf_set_extmark(bufnr, namespace, virt_text.line, 0, {\n                virt_text = virt_text.content,\n            })\n        end\n\n        -- set diagnostics\n        vim.diagnostic.set(\n            namespace,\n            bufnr,\n            vim.tbl_map(function(diagnostic)\n                return {\n                    lnum = diagnostic.line - 1,\n                    col = 0,\n                    message = diagnostic.message,\n                    severity = diagnostic.severity,\n                    source = diagnostic.source,\n                }\n            end, diagnostics),\n            {\n                signs = false,\n            }\n        )\n\n        -- set highlights\n        for i = 1, #highlights do\n            local highlight = highlights[i]\n            vim.api.nvim_buf_add_highlight(\n                bufnr,\n                namespace,\n                highlight.hl_group,\n                highlight.line,\n                highlight.col_start,\n                highlight.col_end\n            )\n        end\n\n        -- set keybinds\n        registered_keybinds = {}\n        for i = 1, #keybinds do\n            local keybind = keybinds[i]\n            if not registered_keybinds[keybind.line] then\n                registered_keybinds[keybind.line] = {}\n            end\n            registered_keybinds[keybind.line][keybind.key] = keybind\n            if not registered_keymaps[keybind.key] then\n                registered_keymaps[keybind.key] = true\n                vim.keymap.set(\"n\", keybind.key, function()\n                    dispatch_effect(keybind.key)\n                end, {\n                    buffer = bufnr,\n                    nowait = true,\n                    silent = true,\n                })\n            end\n        end\n    end\n\n    local function open()\n        bufnr = vim.api.nvim_create_buf(false, true)\n        win_id = vim.api.nvim_open_win(bufnr, true, create_popup_window_opts(window_opts, false))\n\n        local normal_hl = vim.api.nvim_get_hl and vim.api.nvim_get_hl(0, { name = \"Normal\" })\n        local is_nvim_transparent = normal_hl and normal_hl.bg == nil\n\n        if settings.current.ui.backdrop ~= 100 and vim.o.termguicolors and not is_nvim_transparent then\n            backdrop_bufnr = vim.api.nvim_create_buf(false, true)\n            backdrop_win_id = vim.api.nvim_open_win(backdrop_bufnr, false, create_backdrop_window_opts())\n\n            vim.wo[backdrop_win_id].winhighlight = \"Normal:MasonBackdrop\"\n            vim.wo[backdrop_win_id].winblend = settings.current.ui.backdrop\n            vim.bo[backdrop_bufnr].buftype = \"nofile\"\n            -- https://github.com/folke/lazy.nvim/issues/1399\n            vim.bo[backdrop_bufnr].filetype = \"mason_backdrop\"\n            vim.bo[backdrop_bufnr].bufhidden = \"wipe\"\n        end\n\n        vim.api.nvim_create_autocmd(\"CmdLineEnter\", {\n            buffer = bufnr,\n            callback = function()\n                if vim.v.event.cmdtype == \"/\" or vim.v.event.cmdtype == \"?\" then\n                    events:emit \"search:enter\"\n                end\n            end,\n        })\n\n        vim.api.nvim_create_autocmd(\"CmdLineLeave\", {\n            buffer = bufnr,\n            callback = function(args)\n                if vim.v.event.cmdtype == \"/\" or vim.v.event.cmdtype == \"?\" then\n                    events:emit(\"search:leave\", vim.fn.getcmdline())\n                end\n            end,\n        })\n\n        registered_effect_handlers = window_opts.effects\n        registered_keybinds = {}\n        registered_keymaps = {}\n\n        local buf_opts = {\n            modifiable = false,\n            swapfile = false,\n            textwidth = 0,\n            buftype = \"nofile\",\n            bufhidden = \"wipe\",\n            buflisted = false,\n            filetype = filetype,\n            undolevels = -1,\n        }\n\n        local win_opts = {\n            number = false,\n            relativenumber = false,\n            wrap = false,\n            spell = false,\n            foldenable = false,\n            signcolumn = \"no\",\n            colorcolumn = \"\",\n            cursorline = true,\n        }\n\n        -- window options\n        for key, value in pairs(win_opts) do\n            vim.api.nvim_win_set_option(win_id, key, value)\n        end\n\n        if window_opts.winhighlight then\n            vim.api.nvim_win_set_option(win_id, \"winhighlight\", table.concat(window_opts.winhighlight, \",\"))\n        end\n\n        -- buffer options\n        for key, value in pairs(buf_opts) do\n            vim.api.nvim_buf_set_option(bufnr, key, value)\n        end\n\n        vim.cmd [[ syntax clear ]]\n\n        window_mgmt_augroup = vim.api.nvim_create_augroup(\"MasonWindowMgmt\", {})\n        autoclose_augroup = vim.api.nvim_create_augroup(\"MasonWindow\", {})\n\n        vim.api.nvim_create_autocmd({ \"VimResized\" }, {\n            group = window_mgmt_augroup,\n            buffer = bufnr,\n            callback = function()\n                if win_id and vim.api.nvim_win_is_valid(win_id) then\n                    draw(renderer(get_state()))\n                    vim.api.nvim_win_set_config(win_id, create_popup_window_opts(window_opts, true))\n                end\n                if backdrop_win_id and vim.api.nvim_win_is_valid(backdrop_win_id) then\n                    vim.api.nvim_win_set_config(backdrop_win_id, create_backdrop_window_opts())\n                end\n            end,\n        })\n\n        vim.api.nvim_create_autocmd({ \"WinClosed\" }, {\n            once = true,\n            pattern = tostring(win_id),\n            callback = function()\n                if backdrop_win_id and vim.api.nvim_win_is_valid(backdrop_win_id) then\n                    vim.api.nvim_win_close(backdrop_win_id, true)\n                end\n            end,\n        })\n\n        vim.api.nvim_create_autocmd({ \"BufHidden\", \"BufUnload\" }, {\n            group = autoclose_augroup,\n            buffer = bufnr,\n            -- This is for instances where the window remains but the buffer is no longer visible, for example when\n            -- loading another buffer into it (this is basically imitating 'winfixbuf', which was added in 0.10.0).\n            callback = close_window,\n        })\n\n        -- This autocmd is responsible for closing the Mason window(s) when the user focuses another window. It\n        -- essentially behaves as WinLeave except it keeps the Mason window(s) open under certain circumstances.\n        local win_enter_aucmd\n        win_enter_aucmd = vim.api.nvim_create_autocmd({ \"WinEnter\" }, {\n            group = autoclose_augroup,\n            pattern = \"*\",\n            callback = function()\n                local buftype = vim.api.nvim_buf_get_option(0, \"buftype\")\n                -- This allows us to keep the floating window open for things like diagnostic popups, UI inputs á la dressing.nvim, etc.\n                if buftype ~= \"prompt\" and buftype ~= \"nofile\" then\n                    close_window()\n                    vim.api.nvim_del_autocmd(win_enter_aucmd)\n                end\n            end,\n        })\n\n        return win_id\n    end\n\n    return {\n        events = events,\n        ---@param _renderer fun(state: table): table\n        view = function(_renderer)\n            renderer = _renderer\n        end,\n        ---@param _effects table<string, fun()>\n        effects = function(_effects)\n            window_opts.effects = _effects\n        end,\n        ---@generic T : table\n        ---@param initial_state T\n        ---@return fun(mutate_fn: fun(current_state: T)), fun(): T\n        state = function(initial_state)\n            mutate_state, get_state, unsubscribe = state.create_state_container(\n                initial_state,\n                debounced(function(new_state)\n                    draw(renderer(new_state))\n                end)\n            )\n\n            -- we don't need to subscribe to state changes until the window is actually opened\n            unsubscribe(true)\n\n            return mutate_state, get_state\n        end,\n        ---@param opts WindowOpts\n        init = function(opts)\n            assert(renderer ~= nil, \"No view function has been registered. Call .view() before .init().\")\n            assert(unsubscribe ~= nil, \"No state has been registered. Call .state() before .init().\")\n            window_opts = opts\n            has_initiated = true\n        end,\n        open = vim.schedule_wrap(function()\n            log.trace \"Opening window\"\n            assert(has_initiated, \"Display has not been initiated, cannot open.\")\n            if win_id and vim.api.nvim_win_is_valid(win_id) then\n                -- window is already open\n                return\n            end\n            unsubscribe(false)\n            open()\n            draw(renderer(get_state()))\n        end),\n        ---@type fun()\n        close = vim.schedule_wrap(function()\n            assert(has_initiated, \"Display has not been initiated, cannot close.\")\n            unsubscribe(true)\n            log.fmt_trace(\"Closing window win_id=%s, bufnr=%s\", win_id, bufnr)\n            close_window()\n            vim.api.nvim_del_augroup_by_id(window_mgmt_augroup)\n            vim.api.nvim_del_augroup_by_id(autoclose_augroup)\n        end),\n        ---@param pos number[]: (row, col) tuple\n        set_cursor = function(pos)\n            assert(win_id ~= nil, \"Window has not been opened, cannot set cursor.\")\n            return vim.api.nvim_win_set_cursor(win_id, pos)\n        end,\n        ---@return number[]: (row, col) tuple\n        get_cursor = function()\n            assert(win_id ~= nil, \"Window has not been opened, cannot get cursor.\")\n            return vim.api.nvim_win_get_cursor(win_id)\n        end,\n        is_open = function()\n            return win_id ~= nil and vim.api.nvim_win_is_valid(win_id)\n        end,\n        ---@param tag any\n        set_sticky_cursor = function(tag)\n            if output then\n                local new_sticky_cursor_line = output.sticky_cursors.id_map[tag]\n                if new_sticky_cursor_line then\n                    sticky_cursor = tag\n                    local cursor = vim.api.nvim_win_get_cursor(win_id)\n                    vim.api.nvim_win_set_cursor(win_id, { new_sticky_cursor_line, cursor[2] })\n                end\n            end\n        end,\n        get_win_config = function()\n            assert(win_id ~= nil, \"Window has not been opened, cannot get config.\")\n            return vim.api.nvim_win_get_config(win_id)\n        end,\n    }\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/ui/init.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal M = {}\n\n---@alias NodeType\n---| '\"NODE\"'\n---| '\"CASCADING_STYLE\"'\n---| '\"VIRTUAL_TEXT\"'\n---| '\"DIAGNOSTICS\"'\n---| '\"HL_TEXT\"'\n---| '\"KEYBIND_HANDLER\"'\n---| '\"STICKY_CURSOR\"'\n\n---@alias INode Node | HlTextNode | CascadingStyleNode | VirtualTextNode | KeybindHandlerNode | DiagnosticsNode | StickyCursorNode\n\n---@param children INode[]\nfunction M.Node(children)\n    ---@class Node\n    local node = {\n        type = \"NODE\",\n        children = children,\n    }\n    return node\nend\n\n---@param lines_with_span_tuples string[][]|string[]\nfunction M.HlTextNode(lines_with_span_tuples)\n    if type(lines_with_span_tuples[1]) == \"string\" then\n        -- this enables a convenience API for just rendering a single line (with just a single span)\n        lines_with_span_tuples = { { lines_with_span_tuples } }\n    end\n    ---@class HlTextNode\n    local node = {\n        type = \"HL_TEXT\",\n        lines = lines_with_span_tuples,\n    }\n    return node\nend\n\nlocal create_unhighlighted_lines = _.map(function(line)\n    return { { line, \"\" } }\nend)\n\n---@param lines string[]\nfunction M.Text(lines)\n    return M.HlTextNode(create_unhighlighted_lines(lines))\nend\n\n---@alias CascadingStyle\n---| '\"INDENT\"'\n---| '\"CENTERED\"'\n\n---@param styles CascadingStyle[]\n---@param children INode[]\nfunction M.CascadingStyleNode(styles, children)\n    ---@class CascadingStyleNode\n    local node = {\n        type = \"CASCADING_STYLE\",\n        styles = styles,\n        children = children,\n    }\n    return node\nend\n\n---@param virt_text string[][] List of (text, highlight) tuples.\nfunction M.VirtualTextNode(virt_text)\n    ---@class VirtualTextNode\n    local node = {\n        type = \"VIRTUAL_TEXT\",\n        virt_text = virt_text,\n    }\n    return node\nend\n\n---@param diagnostic {message: string, severity: integer, source: string?}\nfunction M.DiagnosticsNode(diagnostic)\n    ---@class DiagnosticsNode\n    local node = {\n        type = \"DIAGNOSTICS\",\n        diagnostic = diagnostic,\n    }\n    return node\nend\n\n---@param condition boolean\n---@param node INode | fun(): INode\n---@param default_val any\nfunction M.When(condition, node, default_val)\n    if condition then\n        if type(node) == \"function\" then\n            return node()\n        else\n            return node\n        end\n    end\n    return default_val or M.Node {}\nend\n\n---@param key string The keymap to register to. Example: \"<CR>\".\n---@param effect string The effect to call when keymap is triggered by the user.\n---@param payload any The payload to pass to the effect handler when triggered.\n---@param is_global boolean? Whether to register the keybind to apply on all lines in the buffer.\nfunction M.Keybind(key, effect, payload, is_global)\n    ---@class KeybindHandlerNode\n    local node = {\n        type = \"KEYBIND_HANDLER\",\n        key = key,\n        effect = effect,\n        payload = payload,\n        is_global = is_global or false,\n    }\n    return node\nend\n\nfunction M.EmptyLine()\n    return M.Text { \"\" }\nend\n\n---@param rows string[][][] A list of rows to include in the table. Each row consists of an array of (text, highlight) tuples (aka spans).\nfunction M.Table(rows)\n    local col_maxwidth = {}\n    for i = 1, #rows do\n        local row = rows[i]\n        for j = 1, #row do\n            local col = row[j]\n            local content = col[1]\n            col_maxwidth[j] = math.max(vim.api.nvim_strwidth(content), col_maxwidth[j] or 0)\n        end\n    end\n\n    for i = 1, #rows do\n        local row = rows[i]\n        for j = 1, #row do\n            local col = row[j]\n            local content = col[1]\n            col[1] = content .. string.rep(\" \", col_maxwidth[j] - vim.api.nvim_strwidth(content) + 1) -- +1 for default minimum padding\n        end\n    end\n\n    return M.HlTextNode(rows)\nend\n\n---@param opts { id: string }\nfunction M.StickyCursor(opts)\n    ---@class StickyCursorNode\n    local node = {\n        type = \"STICKY_CURSOR\",\n        id = opts.id,\n    }\n    return node\nend\n\n---Makes it possible to create stateful animations by progressing from the start of a range to the end.\n---This is done in \"ticks\", in accordance with the provided options.\n---@param opts {range: integer[], delay_ms: integer, start_delay_ms: integer, iteration_delay_ms: integer}\nfunction M.animation(opts)\n    local animation_fn = opts[1]\n    local start_tick, end_tick = opts.range[1], opts.range[2]\n    local is_animating = false\n\n    local function start_animation()\n        if is_animating then\n            return\n        end\n        local tick, start\n\n        tick = function(current_tick)\n            animation_fn(current_tick)\n            if current_tick < end_tick then\n                vim.defer_fn(function()\n                    tick(current_tick + 1)\n                end, opts.delay_ms)\n            else\n                is_animating = false\n                if opts.iteration_delay_ms then\n                    start(opts.iteration_delay_ms)\n                end\n            end\n        end\n\n        start = function(delay_ms)\n            is_animating = true\n            if delay_ms then\n                vim.defer_fn(function()\n                    tick(start_tick)\n                end, delay_ms)\n            else\n                tick(start_tick)\n            end\n        end\n\n        start(opts.start_delay_ms)\n\n        local function cancel()\n            is_animating = false\n        end\n\n        return cancel\n    end\n\n    return start_animation\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-core/ui/state.lua",
    "content": "local M = {}\n\n---@generic T : table\n---@param initial_state T\n---@param subscriber fun(state: T)\nfunction M.create_state_container(initial_state, subscriber)\n    -- we do deepcopy to make sure instances of state containers doesn't mutate the initial state\n    local state = vim.deepcopy(initial_state)\n    local has_unsubscribed = false\n\n    ---@param mutate_fn fun(current_state: table)\n    return function(mutate_fn)\n        mutate_fn(state)\n        if not has_unsubscribed then\n            subscriber(state)\n        end\n    end, function()\n        return state\n    end, function(val)\n        has_unsubscribed = val\n    end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-registry/api.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal fetch = require \"mason-core.fetch\"\n\nlocal api = {}\n\n-- https://github.com/mason-org/mason-registry-api\nlocal BASE_URL = \"https://api.mason-registry.dev\"\n\nlocal stringify_params = _.compose(_.join \"&\", _.map(_.join \"=\"), _.sort_by(_.head), _.to_pairs)\n\n---@alias ApiFetchOpts { params: table<string, any>? }\n\n---@async\n---@param path string\n---@param opts ApiFetchOpts?\n---@return Result # JSON decoded response.\nfunction api.get(path, opts)\n    if opts and opts.params then\n        local params = stringify_params(opts.params)\n        path = (\"%s?%s\"):format(path, params)\n    end\n    return fetch((\"%s%s\"):format(BASE_URL, path), {\n        headers = {\n            Accept = \"application/vnd.mason-registry.v1+json; q=1.0, application/json; q=0.8\",\n        },\n    }):map_catching(vim.json.decode)\nend\n\n---@alias ApiSignature<T> async fun(path_params: T, opts?: ApiFetchOpts): Result\n\n---@param char string\nlocal function percent_encode(char)\n    return (\"%%%x\"):format(string.byte(char, 1, 1))\nend\n\napi.encode_uri_component = _.gsub(\"[!#%$&'%(%)%*%+,/:;=%?@%[%]]\", percent_encode)\n\n---@param path_template string\nlocal function get(path_template)\n    ---@async\n    ---@param path_params table\n    ---@param opts ApiFetchOpts?\n    return function(path_params, opts)\n        local path = path_template:gsub(\"{([%w_%.0-9]+)}\", function(prop)\n            return path_params[prop]\n        end)\n        -- This is done so that test stubs trigger as expected (you have to explicitly match against nil arguments)\n        if opts then\n            return api.get(path, opts)\n        else\n            return api.get(path)\n        end\n    end\nend\n\napi.github = {\n    releases = {\n        ---@type ApiSignature<{ repo: string }>\n        latest = get \"/api/github/{repo}/releases/latest\",\n        ---@type ApiSignature<{ repo: string }>\n        all = get \"/api/github/{repo}/releases/all\",\n    },\n    tags = {\n        ---@type ApiSignature<{ repo: string }>\n        latest = get \"/api/github/{repo}/tags/latest\",\n        ---@type ApiSignature<{ repo: string }>\n        all = get \"/api/github/{repo}/tags/all\",\n    },\n}\n\napi.npm = {\n    versions = {\n        ---@type ApiSignature<{ package: string }>\n        latest = get \"/api/npm/{package}/versions/latest\",\n        ---@type ApiSignature<{ package: string }>\n        all = get \"/api/npm/{package}/versions/all\",\n    },\n}\n\napi.pypi = {\n    versions = {\n        ---@type ApiSignature<{ package: string }>\n        latest = get \"/api/pypi/{package}/versions/latest\",\n        ---@type ApiSignature<{ package: string }>\n        all = get \"/api/pypi/{package}/versions/all\",\n        ---@type ApiSignature<{ package: string, version: string }>\n        get = get \"/api/pypi/{package}/versions/{version}\",\n    },\n}\n\napi.rubygems = {\n    versions = {\n        ---@type ApiSignature<{ gem: string }>\n        latest = get \"/api/rubygems/{gem}/versions/latest\",\n        ---@type ApiSignature<{ gem: string }>\n        all = get \"/api/rubygems/{gem}/versions/all\",\n    },\n}\n\napi.packagist = {\n    versions = {\n        ---@type ApiSignature<{ pkg: string }>\n        latest = get \"/api/packagist/{pkg}/versions/latest\",\n        ---@type ApiSignature<{ pkg: string }>\n        all = get \"/api/packagist/{pkg}/versions/all\",\n    },\n}\n\napi.crate = {\n    versions = {\n        ---@type ApiSignature<{ crate: string }>\n        latest = get \"/api/crate/{crate}/versions/latest\",\n        ---@type ApiSignature<{ crate: string }>\n        all = get \"/api/crate/{crate}/versions/all\",\n    },\n}\n\napi.golang = {\n    versions = {\n        ---@type ApiSignature<{ pkg: string }>\n        all = get \"/api/golang/{pkg}/versions/all\",\n    },\n}\n\napi.openvsx = {\n    versions = {\n        ---@type ApiSignature<{ namespace: string, extension: string }>\n        latest = get \"/api/openvsx/{namespace}/{extension}/versions/latest\",\n        ---@type ApiSignature<{ namespace: string, extension: string }>\n        all = get \"/api/openvsx/{namespace}/{extension}/versions/all\",\n    },\n}\n\nreturn api\n"
  },
  {
    "path": "lua/mason-registry/index/init.lua",
    "content": "return {}\n"
  },
  {
    "path": "lua/mason-registry/init.lua",
    "content": "local EventEmitter = require \"mason-core.EventEmitter\"\nlocal InstallLocation = require \"mason-core.installer.InstallLocation\"\nlocal log = require \"mason-core.log\"\nlocal uv = vim.loop\nlocal LazySourceCollection = require \"mason-registry.sources\"\n\n-- singleton\nlocal Registry = EventEmitter:new()\n\nRegistry.sources = LazySourceCollection:new()\n---@type table<string, string[]>\nRegistry.aliases = {}\n\n---@param pkg_name string\nfunction Registry.is_installed(pkg_name)\n    local stat = uv.fs_stat(InstallLocation.global():package(pkg_name))\n    return stat ~= nil and stat.type == \"directory\"\nend\n\n---Returns an instance of the Package class if the provided package name exists. This function errors if a package\n---cannot be found.\n---@param pkg_name string\n---@return Package\nfunction Registry.get_package(pkg_name)\n    for source in Registry.sources:iterate() do\n        local pkg = source:get_package(pkg_name)\n        if pkg then\n            return pkg\n        end\n    end\n    log.fmt_error(\"Cannot find package %q.\", pkg_name)\n    error((\"Cannot find package %q.\"):format(pkg_name))\nend\n\nfunction Registry.has_package(pkg_name)\n    local ok = pcall(Registry.get_package, pkg_name)\n    return ok\nend\n\nfunction Registry.get_installed_package_names()\n    local fs = require \"mason-core.fs\"\n    if not fs.sync.dir_exists(InstallLocation.global():package()) then\n        return {}\n    end\n    local entries = fs.sync.readdir(InstallLocation:global():package())\n    local directories = {}\n    for _, entry in ipairs(entries) do\n        if entry.type == \"directory\" then\n            directories[#directories + 1] = entry.name\n        end\n    end\n    return directories\nend\n\nfunction Registry.get_installed_packages()\n    return vim.tbl_map(Registry.get_package, Registry.get_installed_package_names())\nend\n\nfunction Registry.get_all_package_names()\n    local pkgs = {}\n    for source in Registry.sources:iterate() do\n        for _, name in ipairs(source:get_all_package_names()) do\n            pkgs[name] = true\n        end\n    end\n    return vim.tbl_keys(pkgs)\nend\n\nfunction Registry.get_all_packages()\n    local _ = require \"mason-core.functional\"\n    local packages =\n        _.uniq_by(_.identity, _.concat(Registry.get_all_package_names(), Registry.get_installed_package_names()))\n    return vim.tbl_map(Registry.get_package, packages)\nend\n\nfunction Registry.get_all_package_specs()\n    local specs = {}\n    for source in Registry.sources:iterate() do\n        for _, spec in ipairs(source:get_all_package_specs()) do\n            if not specs[spec.name] then\n                specs[spec.name] = spec\n            end\n        end\n    end\n    return vim.tbl_values(specs)\nend\n\n---Register aliases for the specified packages\n---@param new_aliases table<string, string[]>\nfunction Registry.register_package_aliases(new_aliases)\n    for pkg_name, pkg_aliases in pairs(new_aliases) do\n        Registry.aliases[pkg_name] = Registry.aliases[pkg_name] or {}\n        for _, alias in pairs(pkg_aliases) do\n            if alias ~= pkg_name then\n                table.insert(Registry.aliases[pkg_name], alias)\n            end\n        end\n    end\nend\n\n---@param name string\nfunction Registry.get_package_aliases(name)\n    return Registry.aliases[name] or {}\nend\n\n---@param callback? fun(success: boolean, updated_registries: RegistrySource[])\nfunction Registry.update(callback)\n    local a = require \"mason-core.async\"\n    local installer = require \"mason-registry.installer\"\n    local noop = function() end\n\n    a.run(function()\n        if installer.channel then\n            log.debug \"Registry update already in progress.\"\n            return installer.channel:receive():get_or_throw()\n        else\n            log.debug \"Updating the registry.\"\n            Registry:emit(\"update:start\", Registry.sources)\n            return installer\n                .install(Registry.sources, function(finished, all)\n                    Registry:emit(\"update:progress\", finished, all)\n                end)\n                :on_success(function(updated_registries)\n                    log.fmt_debug(\"Successfully updated %d registries.\", #updated_registries)\n                    Registry:emit(\"update:success\", updated_registries)\n                end)\n                :on_failure(function(errors)\n                    log.error(\"Failed to update registries.\", errors)\n                    Registry:emit(\"update:failed\", errors)\n                end)\n                :get_or_throw()\n        end\n    end, callback or noop)\nend\n\nlocal REGISTRY_STORE_TTL = 86400 -- 24 hrs\n\n---@param callback? fun(success: boolean, updated_registries: RegistrySource[])\nfunction Registry.refresh(callback)\n    log.debug \"Refreshing the registry.\"\n    local a = require \"mason-core.async\"\n    local installer = require \"mason-registry.installer\"\n\n    local state = installer.get_registry_state()\n    if state and Registry.sources:is_all_installed() then\n        local registry_age = os.time() - state.timestamp\n\n        if registry_age <= REGISTRY_STORE_TTL and state.checksum == Registry.sources:checksum() then\n            log.fmt_debug(\n                \"Registry refresh is not necessary yet. Registry age=%d, checksum=%s\",\n                registry_age,\n                state.checksum\n            )\n            if callback then\n                callback(true, {})\n            end\n            return\n        end\n    end\n\n    local function async_update()\n        return a.wait(function(resolve, reject)\n            Registry.update(function(success, result)\n                if success then\n                    resolve(result)\n                else\n                    reject(result)\n                end\n            end)\n        end)\n    end\n\n    if not callback then\n        -- We don't want to error in the synchronous version because of how this function is recommended to be used in\n        -- 3rd party code. If accessing the update result is required, users are recommended to pass a callback.\n        pcall(a.run_blocking, async_update)\n    else\n        a.run(async_update, callback)\n    end\nend\n\nreturn Registry\n"
  },
  {
    "path": "lua/mason-registry/installer.lua",
    "content": "local a = require \"mason-core.async\"\nlocal log = require \"mason-core.log\"\nlocal OneShotChannel = require(\"mason-core.async.control\").OneShotChannel\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal fs = require \"mason-core.fs\"\nlocal path = require \"mason-core.path\"\n\nlocal M = {}\n\nlocal STATE_FILE = path.concat { vim.fn.stdpath \"cache\", \"mason-registry-update\" }\n\n---@param sources LazySourceCollection\n---@param time integer\nlocal function update_registry_state(sources, time)\n    log.trace(\"Updating registry state\", sources, time)\n    local dir = vim.fn.fnamemodify(STATE_FILE, \":h\")\n    if not fs.sync.dir_exists(dir) then\n        fs.sync.mkdirp(dir)\n    end\n    fs.sync.write_file(STATE_FILE, _.join(\"\\n\", { sources:checksum(), tostring(time) }))\nend\n\n---@return { checksum: string, timestamp: integer }?\nfunction M.get_registry_state()\n    if fs.sync.file_exists(STATE_FILE) then\n        local parse_state_file =\n            _.compose(_.evolve { timestamp = tonumber }, _.zip_table { \"checksum\", \"timestamp\" }, _.split \"\\n\")\n        return parse_state_file(fs.sync.read_file(STATE_FILE))\n    end\nend\n\n---@async\n---@param sources LazySourceCollection\n---@param on_progress fun(finished: RegistrySource[], all: RegistrySource[])\n---@return Result # Result<RegistrySource[]>\nfunction M.install(sources, on_progress)\n    log.debug(\"Installing registries.\", sources)\n    assert(not M.channel, \"Cannot install when channel is active.\")\n    M.channel = OneShotChannel:new()\n\n    local finished_registries = {}\n    local registries = sources:to_list { include_uninstalled = true, include_synthesized = false }\n\n    local results = {\n        a.wait_all(_.map(\n            ---@param source RegistrySource\n            function(source)\n                return function()\n                    log.trace(\"Installing registry.\", source)\n                    return source\n                        :install()\n                        :map(_.always(source))\n                        :map_err(function(err)\n                            return (\"%s failed to install: %s\"):format(source, err)\n                        end)\n                        :on_success(function()\n                            table.insert(finished_registries, source)\n                            on_progress(finished_registries, registries)\n                        end)\n                end\n            end,\n            registries\n        )),\n    }\n\n    local any_failed = _.any(Result.is_failure, results)\n\n    if any_failed then\n        local unwrap_failures = _.compose(_.map(Result.err_or_nil), _.filter(Result.is_failure))\n        local result = Result.failure(unwrap_failures(results))\n        M.channel:send(result)\n        M.channel = nil\n        return result\n    else\n        local result = Result.success(_.map(Result.get_or_nil, results))\n        a.scheduler()\n        update_registry_state(sources, os.time())\n        M.channel:send(result)\n        M.channel = nil\n        return result\n    end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-registry/sources/file.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal async_uv = require \"mason-core.async.uv\"\nlocal fs = require \"mason-core.fs\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal process = require \"mason-core.process\"\nlocal spawn = require \"mason-core.spawn\"\nlocal util = require \"mason-registry.sources.util\"\n\n---@class FileRegistrySourceSpec\n---@field id string\n---@field path string\n\n---@class FileRegistrySource : RegistrySource\n---@field spec FileRegistrySourceSpec\n---@field root_dir string\n---@field buffer { specs: RegistryPackageSpec[], instances: table<string, Package> }?\nlocal FileRegistrySource = {}\nFileRegistrySource.__index = FileRegistrySource\n\n---@param spec FileRegistrySourceSpec\nfunction FileRegistrySource:new(spec)\n    ---@type FileRegistrySource\n    local instance = {}\n    setmetatable(instance, self)\n    instance.id = spec.id\n    instance.spec = spec\n    return instance\nend\n\nfunction FileRegistrySource:is_installed()\n    return self.buffer ~= nil\nend\n\n---@return RegistryPackageSpec[]\nfunction FileRegistrySource:get_all_package_specs()\n    return _.filter_map(util.map_registry_spec, self:get_buffer().specs)\nend\n\n---@param specs RegistryPackageSpec[]\nfunction FileRegistrySource:reload(specs)\n    self.buffer = _.assoc(\"specs\", specs, self.buffer or {})\n    self.buffer.instances = _.compose(\n        _.index_by(_.prop \"name\"),\n        _.map(util.hydrate_package(self, self.buffer.instances or {}))\n    )(self:get_all_package_specs())\n    return self.buffer\nend\n\nfunction FileRegistrySource:get_buffer()\n    return self.buffer or {\n        specs = {},\n        instances = {},\n    }\nend\n\n---@param pkg_name string\n---@return Package?\nfunction FileRegistrySource:get_package(pkg_name)\n    return self:get_buffer().instances[pkg_name]\nend\n\nfunction FileRegistrySource:get_all_package_names()\n    return _.map(_.prop \"name\", self:get_all_package_specs())\nend\n\n---@async\nfunction FileRegistrySource:install()\n    return Result.try(function(try)\n        a.scheduler()\n        if vim.fn.executable \"yq\" ~= 1 then\n            return Result.failure \"yq is not installed.\"\n        end\n        local yq = vim.fn.exepath \"yq\"\n\n        local registry_dir = vim.fn.expand(self.spec.path) --[[@as string]]\n        local packages_dir = path.concat { registry_dir, \"packages\" }\n        if not fs.async.dir_exists(registry_dir) then\n            return Result.failure((\"Directory %s does not exist.\"):format(registry_dir))\n        end\n\n        if not fs.async.dir_exists(packages_dir) then\n            return Result.failure \"packages/ directory is missing.\"\n        end\n\n        ---@type ReaddirEntry[]\n        local entries = _.filter(_.prop_eq(\"type\", \"directory\"), fs.async.readdir(packages_dir))\n\n        local streaming_parser = coroutine.wrap(function()\n            local buffer = \"\"\n            while true do\n                local delim = buffer:find(\"\\n\", 1, true)\n                if delim then\n                    local content = buffer:sub(1, delim - 1)\n                    buffer = buffer:sub(delim + 1)\n                    local chunk = coroutine.yield(content)\n                    buffer = buffer .. chunk\n                else\n                    local chunk = coroutine.yield()\n                    buffer = buffer .. chunk\n                end\n            end\n        end)\n\n        -- Initialize parser coroutine.\n        streaming_parser()\n\n        local specs = {}\n        local stderr_buffer = {}\n        local parse_failures = 0\n\n        ---@param raw_spec string\n        local function handle_spec(raw_spec)\n            local ok, result = pcall(vim.json.decode, raw_spec)\n            if ok then\n                specs[#specs + 1] = result\n            else\n                log.fmt_error(\"Failed to parse JSON, err=%s, json=%s\", result, raw_spec)\n                parse_failures = parse_failures + 1\n            end\n        end\n\n        try(spawn\n            [yq]({\n                \"-I0\", -- output one document per line\n                { \"-o\", \"json\" },\n                stdio_sink = process.StdioSink:new {\n                    stdout = function(chunk)\n                        local raw_spec = streaming_parser(chunk)\n                        if raw_spec then\n                            handle_spec(raw_spec)\n                        end\n                    end,\n                    stderr = function(chunk)\n                        stderr_buffer[#stderr_buffer + 1] = chunk\n                    end,\n                },\n                on_spawn = a.scope(function(_, stdio)\n                    local stdin = stdio[1]\n                    for _, entry in ipairs(entries) do\n                        local contents = fs.async.read_file(path.concat { packages_dir, entry.name, \"package.yaml\" })\n                        async_uv.write(stdin, contents)\n                    end\n                    async_uv.shutdown(stdin)\n                    async_uv.close(stdin)\n                end),\n            })\n            :map_err(function()\n                return (\"Failed to parse package YAML: %s\"):format(table.concat(stderr_buffer, \"\"))\n            end))\n\n        -- Exhaust parser coroutine.\n        for raw_spec in\n            function()\n                return streaming_parser \"\"\n            end\n        do\n            handle_spec(raw_spec)\n        end\n\n        if parse_failures > 0 then\n            return Result.failure((\"Failed to parse %d packages.\"):format(parse_failures))\n        end\n\n        return specs\n    end)\n        :on_success(function(specs)\n            self:reload(specs)\n        end)\n        :on_failure(function(err)\n            log.fmt_error(\"Failed to install registry %s. %s\", self, err)\n        end)\nend\n\nfunction FileRegistrySource:get_display_name()\n    if self:is_installed() then\n        return (\"local: %s\"):format(self.spec.path)\n    else\n        return (\"local: %s [uninstalled]\"):format(self.spec.path)\n    end\nend\n\nfunction FileRegistrySource:serialize()\n    return {\n        proto = \"file\",\n        path = self.id,\n    }\nend\n\n---@param other FileRegistrySource\nfunction FileRegistrySource:is_same_location(other)\n    return vim.fn.expand(self.spec.path) == vim.fn.expand(other.spec.path)\nend\n\nfunction FileRegistrySource:__tostring()\n    return (\"FileRegistrySource(path=%s)\"):format(self.spec.path)\nend\n\nreturn FileRegistrySource\n"
  },
  {
    "path": "lua/mason-registry/sources/github.lua",
    "content": "local InstallLocation = require \"mason-core.installer.InstallLocation\"\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal fetch = require \"mason-core.fetch\"\nlocal fs = require \"mason-core.fs\"\nlocal log = require \"mason-core.log\"\nlocal path = require \"mason-core.path\"\nlocal providers = require \"mason-core.providers\"\nlocal settings = require \"mason.settings\"\nlocal util = require \"mason-registry.sources.util\"\n\n-- Parse sha256sum text output to a table<filename: string, sha256sum: string> structure\nlocal parse_checksums = _.compose(_.from_pairs, _.map(_.compose(_.reverse, _.split \"  \")), _.split \"\\n\", _.trim)\n\n---@class GitHubRegistrySourceSpec\n---@field id string\n---@field namespace string\n---@field name string\n---@field version string?\n\n---@class GitHubRegistrySource : RegistrySource\n---@field spec GitHubRegistrySourceSpec\n---@field repo string\n---@field root_dir string\n---@field private data_file string\n---@field private info_file string\n---@field buffer table<string, Package>?\nlocal GitHubRegistrySource = {}\nGitHubRegistrySource.__index = GitHubRegistrySource\n\n---@param spec GitHubRegistrySourceSpec\nfunction GitHubRegistrySource:new(spec)\n    ---@type GitHubRegistrySource\n    local instance = {}\n    setmetatable(instance, GitHubRegistrySource)\n    local root_dir = InstallLocation.global():registry(path.concat { \"github\", spec.namespace, spec.name })\n    instance.id = spec.id\n    instance.spec = spec\n    instance.repo = (\"%s/%s\"):format(spec.namespace, spec.name)\n    instance.root_dir = root_dir\n    instance.data_file = path.concat { root_dir, \"registry.json\" }\n    instance.info_file = path.concat { root_dir, \"info.json\" }\n    return instance\nend\n\nfunction GitHubRegistrySource:is_installed()\n    return fs.sync.file_exists(self.data_file) and fs.sync.file_exists(self.info_file)\nend\n\n---@return RegistryPackageSpec[]\nfunction GitHubRegistrySource:get_all_package_specs()\n    if not self:is_installed() then\n        return {}\n    end\n    local data = vim.json.decode(fs.sync.read_file(self.data_file)) --[[@as RegistryPackageSpec[] ]]\n    return _.filter_map(util.map_registry_spec, data)\nend\n\nfunction GitHubRegistrySource:reload()\n    if not self:is_installed() then\n        return\n    end\n    self.buffer = _.compose(_.index_by(_.prop \"name\"), _.map(util.hydrate_package(self, self.buffer or {})))(\n        self:get_all_package_specs()\n    )\n    return self.buffer\nend\n\nfunction GitHubRegistrySource:get_buffer()\n    return self.buffer or self:reload() or {}\nend\n\n---@param pkg string\n---@return Package?\nfunction GitHubRegistrySource:get_package(pkg)\n    return self:get_buffer()[pkg]\nend\n\nfunction GitHubRegistrySource:get_all_package_names()\n    return _.map(_.prop \"name\", self:get_all_package_specs())\nend\n\n---@async\nfunction GitHubRegistrySource:install()\n    local zzlib = require \"mason-vendor.zzlib\"\n\n    return Result.try(function(try)\n        local version = self.spec.version\n        if self:is_installed() and self:get_info().version == version then\n            -- Fixed version is already installed - nothing to update\n            return\n        end\n\n        if not fs.async.dir_exists(self.root_dir) then\n            log.debug(\"Creating registry directory\", self)\n            try(Result.pcall(fs.async.mkdirp, self.root_dir))\n        end\n\n        if version == nil then\n            log.trace(\"Resolving latest version for registry\", self)\n            ---@type GitHubRelease\n            local release = try(\n                providers.github\n                    .get_latest_release(self.repo)\n                    :map_err(_.always \"Failed to fetch latest registry version from GitHub API.\")\n            )\n            version = release.tag_name\n            log.trace(\"Resolved latest registry version\", self, version)\n        end\n\n        local zip_file = path.concat { self.root_dir, \"registry.json.zip\" }\n        try(fetch(settings.current.github.download_url_template:format(self.repo, version, \"registry.json.zip\"), {\n            out_file = zip_file,\n        }):map_err(_.always \"Failed to download registry archive.\"))\n        local zip_buffer = fs.async.read_file(zip_file)\n        local registry_contents = try(\n            Result.pcall(zzlib.unzip, zip_buffer, \"registry.json\")\n                :on_failure(_.partial(log.error, \"Failed to unpack registry archive.\"))\n                :map_err(_.always \"Failed to unpack registry archive.\")\n        )\n        pcall(fs.async.unlink, zip_file)\n\n        local checksums = try(\n            fetch(settings.current.github.download_url_template:format(self.repo, version, \"checksums.txt\")):map_err(\n                _.always \"Failed to download checksums.txt.\"\n            )\n        )\n\n        try(Result.pcall(fs.async.write_file, self.data_file, registry_contents))\n        try(Result.pcall(\n            fs.async.write_file,\n            self.info_file,\n            vim.json.encode {\n                checksums = parse_checksums(checksums),\n                version = version,\n                download_timestamp = os.time(),\n            }\n        ))\n    end)\n        :on_success(function()\n            self:reload()\n        end)\n        :on_failure(function(err)\n            log.fmt_error(\"Failed to install registry %s. %s\", self, err)\n        end)\nend\n\n---@return { checksums: table<string, string>, version: string, download_timestamp: integer }\nfunction GitHubRegistrySource:get_info()\n    return vim.json.decode(fs.sync.read_file(self.info_file))\nend\n\nfunction GitHubRegistrySource:get_display_name()\n    if self:is_installed() then\n        local info = self:get_info()\n        return (\"github.com/%s version: %s\"):format(self.repo, info.version)\n    else\n        return (\"github.com/%s [uninstalled]\"):format(self.repo)\n    end\nend\n\nfunction GitHubRegistrySource:serialize()\n    local info = self:get_info()\n    return {\n        proto = \"github\",\n        namespace = self.spec.namespace,\n        name = self.spec.name,\n        version = info.version,\n        checksums = info.checksums,\n    }\nend\n\n---@param other GitHubRegistrySource\nfunction GitHubRegistrySource:is_same_location(other)\n    return self.spec.namespace == other.spec.namespace and self.spec.name == other.spec.name\nend\n\nfunction GitHubRegistrySource:__tostring()\n    if self.spec.version then\n        return (\"GitHubRegistrySource(repo=%s, version=%s)\"):format(self.repo, self.spec.version)\n    else\n        return (\"GitHubRegistrySource(repo=%s)\"):format(self.repo)\n    end\nend\n\nreturn GitHubRegistrySource\n"
  },
  {
    "path": "lua/mason-registry/sources/init.lua",
    "content": "local log = require \"mason-core.log\"\n\n---@class RegistrySource\n---@field id string\n---@field get_package fun(self: RegistrySource, pkg_name: string): Package?\n---@field get_all_package_names fun(self: RegistrySource): string[]\n---@field get_all_package_specs fun(self: RegistrySource): RegistryPackageSpec[]\n---@field get_display_name fun(self: RegistrySource): string\n---@field is_installed fun(self: RegistrySource): boolean\n---@field install fun(self: RegistrySource): Result\n---@field serialize fun(self: RegistrySource): InstallReceiptRegistry\n---@field is_same_location fun(self: RegistrySource, other: RegistrySource): boolean\n\n---@alias RegistrySourceType '\"github\"' | '\"lua\"' | '\"file\"' | '\"synthesized\"'\n\n---@class LazySource\n---@field type RegistrySourceType\n---@field id string\n---@field init fun(id: string): RegistrySource\nlocal LazySource = {}\nLazySource.__index = LazySource\n\n---@param id string\nfunction LazySource.GitHub(id)\n    local namespace, name = id:match \"^(.+)/(.+)$\"\n    if not namespace or not name then\n        error((\"Failed to parse repository from GitHub registry: %q\"):format(id), 0)\n    end\n    local name, version = unpack(vim.split(name, \"@\"))\n    local GitHubRegistrySource = require \"mason-registry.sources.github\"\n    return GitHubRegistrySource:new {\n        id = id,\n        namespace = namespace,\n        name = name,\n        version = version,\n    }\nend\n\n---@param id string\nfunction LazySource.Lua(id)\n    local LuaRegistrySource = require \"mason-registry.sources.lua\"\n    return LuaRegistrySource:new {\n        id = id,\n        mod = id,\n    }\nend\n\n---@param id string\nfunction LazySource.File(id)\n    local FileRegistrySource = require \"mason-registry.sources.file\"\n    return FileRegistrySource:new {\n        id = id,\n        path = id,\n    }\nend\n\nfunction LazySource.Synthesized()\n    local SynthesizedSource = require \"mason-registry.sources.synthesized\"\n    return SynthesizedSource:new()\nend\n\n---@param type RegistrySourceType\n---@param id string\n---@param init fun(id: string): RegistrySource\nfunction LazySource:new(type, id, init)\n    local instance = setmetatable({}, self)\n    instance.type = type\n    instance.id = id\n    instance.init = init\n    return instance\nend\n\nfunction LazySource:get()\n    if not self.instance then\n        self.instance = self.init(self.id)\n    end\n    return self.instance\nend\n\n---@param other LazySource\nfunction LazySource:is_same_location(other)\n    if self.type == other.type then\n        return self:get():is_same_location(other:get())\n    end\n    return false\nend\n\nfunction LazySource:get_full_id()\n    return (\"%s:%s\"):format(self.type, self.id)\nend\n\nfunction LazySource:__tostring()\n    return (\"LazySource(type=%s, id=%s)\"):format(self.type, self.id)\nend\n\n---@param str string\nlocal function split_once_left(str, char)\n    for i = 1, #str do\n        if str:sub(i, i) == char then\n            local segment = str:sub(1, i - 1)\n            return segment, str:sub(i + 1)\n        end\n    end\n    return str\nend\n\n---@param registry_id string\nlocal function parse(registry_id)\n    local type, id = split_once_left(registry_id, \":\")\n    assert(id, (\"Malformed registry %q\"):format(registry_id))\n    if type == \"github\" then\n        return LazySource:new(type, id, LazySource.GitHub)\n    elseif type == \"lua\" then\n        return LazySource:new(type, id, LazySource.Lua)\n    elseif type == \"file\" then\n        return LazySource:new(type, id, LazySource.File)\n    end\n    error((\"Unknown registry type: %s\"):format(type))\nend\n\n---@class LazySourceCollection\n---@field list LazySource[]\n---@field synthesized LazySource\nlocal LazySourceCollection = {}\nLazySourceCollection.__index = LazySourceCollection\n\n---@return LazySourceCollection\nfunction LazySourceCollection:new()\n    local instance = {}\n    setmetatable(instance, self)\n    instance.list = {}\n    instance.synthesized = LazySource:new(\"synthesized\", \"synthesized\", LazySource.Synthesized)\n    return instance\nend\n\n---@param registry string\nfunction LazySourceCollection:append(registry)\n    self:unique_insert(parse(registry))\n    return self\nend\n\n---@param registry string\nfunction LazySourceCollection:prepend(registry)\n    self:unique_insert(parse(registry), 1)\n    return self\nend\n\n---@param source LazySource\n---@param idx? integer\nfunction LazySourceCollection:unique_insert(source, idx)\n    idx = idx or #self.list + 1\n    if idx > 1 then\n        for i = 1, math.min(idx, #self.list) do\n            if self.list[i]:is_same_location(source) then\n                log.fmt_warn(\n                    \"Ignoring duplicate registry entry %s (duplicate of %s)\",\n                    source:get_full_id(),\n                    self.list[i]:get_full_id()\n                )\n                return\n            end\n        end\n    end\n    for i = #self.list, idx, -1 do\n        if self.list[i]:is_same_location(source) then\n            table.remove(self.list, i)\n        end\n    end\n    table.insert(self.list, idx, source)\nend\n\nfunction LazySourceCollection:is_all_installed()\n    for source in self:iterate { include_uninstalled = true } do\n        if not source:is_installed() then\n            return false\n        end\n    end\n    return true\nend\n\nfunction LazySourceCollection:checksum()\n    ---@type string[]\n    local registry_ids = vim.tbl_map(\n        ---@param source LazySource\n        function(source)\n            return source.id\n        end,\n        self.list\n    )\n    table.sort(registry_ids)\n    return vim.fn.sha256(table.concat(registry_ids, \"\"))\nend\n\n---@alias LazySourceCollectionIterate { include_uninstalled?: boolean, include_synthesized?: boolean }\n\n---@param opts? LazySourceCollectionIterate\nfunction LazySourceCollection:iterate(opts)\n    opts = opts or {}\n\n    local idx = 1\n    return function()\n        while idx <= #self.list do\n            local source = self.list[idx]:get()\n            idx = idx + 1\n            if opts.include_uninstalled or source:is_installed() then\n                return source\n            end\n        end\n\n        -- We've exhausted the true registry sources, fall back to the synthesized registry source.\n        if idx == #self.list + 1 and opts.include_synthesized ~= false then\n            idx = idx + 1\n            return self.synthesized:get()\n        end\n    end\nend\n\n---@param opts? LazySourceCollectionIterate\nfunction LazySourceCollection:to_list(opts)\n    opts = opts or {}\n    local list = {}\n    for source in self:iterate(opts) do\n        table.insert(list, source)\n    end\n    return list\nend\n\n---@param idx integer\nfunction LazySourceCollection:get(idx)\n    return self.list[idx]\nend\n\nfunction LazySourceCollection:size()\n    return #self.list\nend\n\nfunction LazySourceCollection:__tostring()\n    return (\"LazySourceCollection(list={%s})\"):format(table.concat(vim.tbl_map(tostring, self.list), \", \"))\nend\n\nreturn LazySourceCollection\n"
  },
  {
    "path": "lua/mason-registry/sources/lua.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal util = require \"mason-registry.sources.util\"\n\n---@class LuaRegistrySourceSpec\n---@field id string\n---@field mod string\n\n---@class LuaRegistrySource : RegistrySource\n---@field private spec LuaRegistrySourceSpec\n---@field buffer { specs: RegistryPackageSpec[], instances: table<string, Package> }?\nlocal LuaRegistrySource = {}\nLuaRegistrySource.__index = LuaRegistrySource\n\n---@param spec LuaRegistrySourceSpec\nfunction LuaRegistrySource:new(spec)\n    ---@type LuaRegistrySource\n    local instance = {}\n    setmetatable(instance, LuaRegistrySource)\n    instance.id = spec.id\n    instance.spec = spec\n    return instance\nend\n\n---@param pkg_name string\n---@return Package?\nfunction LuaRegistrySource:get_package(pkg_name)\n    return self:get_buffer().instances[pkg_name]\nend\n\n---@param specs RegistryPackageSpec[]\nfunction LuaRegistrySource:reload(specs)\n    self.buffer = _.assoc(\"specs\", specs, self.buffer or {})\n    self.buffer.instances = _.compose(\n        _.index_by(_.prop \"name\"),\n        _.map(util.hydrate_package(self, self.buffer.instances or {}))\n    )(self:get_all_package_specs())\n    return self.buffer\nend\n\nfunction LuaRegistrySource:install()\n    return Result.try(function(try)\n        local index = try(Result.pcall(require, self.spec.mod))\n        ---@type RegistryPackageSpec[]\n        local specs = {}\n\n        for _, mod in pairs(index) do\n            table.insert(specs, try(Result.pcall(require, mod)))\n        end\n\n        try(Result.pcall(self.reload, self, specs))\n    end)\nend\n\n---@return string[]\nfunction LuaRegistrySource:get_all_package_names()\n    return _.map(_.prop \"name\", self:get_all_package_specs())\nend\n\n---@return RegistryPackageSpec[]\nfunction LuaRegistrySource:get_all_package_specs()\n    return _.filter_map(util.map_registry_spec, self:get_buffer().specs)\nend\n\nfunction LuaRegistrySource:get_buffer()\n    return self.buffer or {\n        specs = {},\n        instances = {},\n    }\nend\n\nfunction LuaRegistrySource:is_installed()\n    return self.buffer ~= nil\nend\n\nfunction LuaRegistrySource:get_display_name()\n    if self:is_installed() then\n        return (\"require(%q)\"):format(self.spec.mod)\n    else\n        return (\"require(%q) [uninstalled]\"):format(self.spec.mod)\n    end\nend\n\nfunction LuaRegistrySource:serialize()\n    return {\n        proto = \"lua\",\n        mod = self.id,\n    }\nend\n\n---@param other LuaRegistrySource\nfunction LuaRegistrySource:is_same_location(other)\n    return self.id == other.id\nend\n\nfunction LuaRegistrySource:__tostring()\n    return (\"LuaRegistrySource(mod=%s)\"):format(self.spec.mod)\nend\n\nreturn LuaRegistrySource\n"
  },
  {
    "path": "lua/mason-registry/sources/synthesized.lua",
    "content": "local Package = require \"mason-core.package\"\nlocal Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal InstallReceipt = require(\"mason-core.receipt\").InstallReceipt\nlocal InstallLocation = require \"mason-core.installer.InstallLocation\"\nlocal fs = require \"mason-core.fs\"\nlocal log = require \"mason-core.log\"\n\n---@class SynthesizedRegistrySource : RegistrySource\n---@field buffer table<string, Package>\nlocal SynthesizedRegistrySource = {}\nSynthesizedRegistrySource.__index = SynthesizedRegistrySource\n\nfunction SynthesizedRegistrySource:new()\n    ---@type SynthesizedRegistrySource\n    local instance = {}\n    setmetatable(instance, self)\n    instance.buffer = {}\n    return instance\nend\n\nfunction SynthesizedRegistrySource:is_installed()\n    return true\nend\n\n---@return RegistryPackageSpec[]\nfunction SynthesizedRegistrySource:get_all_package_specs()\n    return {}\nend\n\n---@param pkg_name string\n---@param receipt InstallReceipt\n---@return Package\nfunction SynthesizedRegistrySource:load_package(pkg_name, receipt)\n    local installed_version = receipt:get_installed_package_version()\n    local source = {\n        id = (\"pkg:mason/%s@%s\"):format(pkg_name, installed_version or \"N%2FA\"), -- N%2FA = N/A\n        install = function()\n            error(\"This package can no longer be installed because it has been removed from the registry.\", 0)\n        end,\n    }\n    ---@type RegistryPackageSpec\n    local spec = {\n        schema = \"registry+v1\",\n        name = pkg_name,\n        description = \"\",\n        categories = {},\n        languages = {},\n        homepage = \"\",\n        licenses = {},\n        deprecation = {\n            since = installed_version or \"N/A\",\n            message = \"This package has been removed from the registry.\",\n        },\n        source = source,\n    }\n    local existing_pkg = self.buffer[pkg_name]\n    if existing_pkg then\n        existing_pkg:update(spec, self)\n        return existing_pkg\n    else\n        local pkg = Package:new(spec, self)\n        self.buffer[pkg_name] = pkg\n        return pkg\n    end\nend\n\n---@param pkg_name string\n---@return Package?\nfunction SynthesizedRegistrySource:get_package(pkg_name)\n    local receipt_path = InstallLocation.global():receipt(pkg_name)\n    if fs.sync.file_exists(receipt_path) then\n        local ok, receipt_json = pcall(vim.json.decode, fs.sync.read_file(receipt_path))\n        if ok then\n            local receipt = InstallReceipt.from_json(receipt_json)\n            return self:load_package(pkg_name, receipt)\n        else\n            log.error(\"Failed to decode package receipt\", pkg_name, receipt_json)\n        end\n    end\nend\n\nfunction SynthesizedRegistrySource:get_all_package_names()\n    return vim.tbl_keys(self.buffer)\nend\n\n---@async\nfunction SynthesizedRegistrySource:install()\n    return Result.success()\nend\n\nfunction SynthesizedRegistrySource:get_display_name()\n    return \"SynthesizedRegistrySource\"\nend\n\nfunction SynthesizedRegistrySource:serialize()\n    return {}\nend\n\n---@param other SynthesizedRegistrySource\nfunction SynthesizedRegistrySource:is_same_location(other)\n    return true\nend\n\nfunction SynthesizedRegistrySource:__tostring()\n    return \"SynthesizedRegistrySource\"\nend\n\nreturn SynthesizedRegistrySource\n"
  },
  {
    "path": "lua/mason-registry/sources/util.lua",
    "content": "local Optional = require \"mason-core.optional\"\nlocal Package = require \"mason-core.package\"\nlocal _ = require \"mason-core.functional\"\nlocal compiler = require \"mason-core.installer.compiler\"\nlocal log = require \"mason-core.log\"\n\nlocal M = {}\n\n---@param spec RegistryPackageSpec\nfunction M.map_registry_spec(spec)\n    spec.schema = spec.schema or \"registry+v1\"\n\n    if not compiler.SCHEMA_CAP[spec.schema] then\n        log.fmt_debug(\"Excluding package=%s with unsupported schema_version=%s\", spec.name, spec.schema)\n        return Optional.empty()\n    end\n\n    return Optional.of(spec)\nend\n\n---@param registry RegistrySource\n---@param buffer table<string, Package>\n---@param spec RegistryPackageSpec\nM.hydrate_package = _.curryN(function(registry, buffer, spec)\n    -- hydrate Pkg.Lang/License index\n    _.each(function(lang)\n        local _ = Package.Lang[lang]\n    end, spec.languages)\n    _.each(function(lang)\n        local _ = Package.License[lang]\n    end, spec.licenses)\n\n    local existing_instance = buffer[spec.name]\n    if existing_instance then\n        -- Apply spec to the existing Package instances. This is important as to not have lingering package instances.\n        existing_instance:update(spec, registry)\n        return existing_instance\n    end\n\n    local new_instance = Package:new(spec, registry)\n    return new_instance\nend, 3)\n\nreturn M\n"
  },
  {
    "path": "lua/mason-test/helpers.lua",
    "content": "local InstallContext = require \"mason-core.installer.context\"\nlocal InstallHandle = require \"mason-core.installer.InstallHandle\"\nlocal InstallLocation = require \"mason-core.installer.InstallLocation\"\nlocal Result = require \"mason-core.result\"\nlocal a = require \"mason-core.async\"\nlocal registry = require \"mason-registry\"\nlocal spy = require \"luassert.spy\"\n\nlocal M = {}\n\n---@param opts? { install_opts?: PackageInstallOpts, package?: string }\nfunction M.create_context(opts)\n    local pkg = registry.get_package(opts and opts.package or \"dummy\")\n    local handle = InstallHandle:new(pkg, InstallLocation.global())\n    local context = InstallContext:new(handle, opts and opts.install_opts or {})\n    context.spawn = setmetatable({}, {\n        __index = function(s, cmd)\n            s[cmd] = spy.new(function()\n                return Result.success { stdout = nil, stderr = nil }\n            end)\n            return s[cmd]\n        end,\n    })\n    context.cwd:initialize():get_or_throw()\n    return context\nend\n\n---@param pkg AbstractPackage\n---@param opts? PackageInstallOpts\nfunction M.sync_install(pkg, opts)\n    return a.run_blocking(function()\n        return a.wait(function(resolve, reject)\n            pkg:install(opts, function(success, result)\n                (success and resolve or reject)(result)\n            end)\n        end)\n    end)\nend\n\n---@param pkg AbstractPackage\n---@param opts? PackageUninstallOpts\nfunction M.sync_uninstall(pkg, opts)\n    return a.run_blocking(function()\n        return a.wait(function(resolve, reject)\n            pkg:uninstall(opts, function(success, result)\n                (success and resolve or reject)(result)\n            end)\n        end)\n    end)\nend\n\n---@param runner InstallRunner\n---@param opts PackageInstallOpts\nfunction M.sync_runner_execute(runner, opts)\n    local callback = spy.new()\n    runner:execute(opts, callback)\n    assert.wait(function()\n        assert.spy(callback).was_called()\n    end)\n    return callback\nend\n\nreturn M\n"
  },
  {
    "path": "lua/mason-vendor/semver.lua",
    "content": "-- stylua: ignore start\n\nlocal semver = {\n  _VERSION     = '1.2.1',\n  _DESCRIPTION = 'semver for Lua',\n  _URL         = 'https://github.com/kikito/semver.lua',\n  _LICENSE     = [[\n    MIT LICENSE\n\n    Copyright (c) 2015 Enrique García Cota\n\n    Permission is hereby granted, free of charge, to any person obtaining a\n    copy of tother software and associated documentation files (the\n    \"Software\"), to deal in the Software without restriction, including\n    without limitation the rights to use, copy, modify, merge, publish,\n    distribute, sublicense, and/or sell copies of the Software, and to\n    permit persons to whom the Software is furnished to do so, subject to\n    the following conditions:\n\n    The above copyright notice and tother permission notice shall be included\n    in all copies or substantial portions of the Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n  ]]\n}\n\nlocal function checkPositiveInteger(number, name)\n  assert(number >= 0, name .. ' must be a valid positive number')\n  assert(math.floor(number) == number, name .. ' must be an integer')\nend\n\nlocal function present(value)\n  return value and value ~= ''\nend\n\n-- splitByDot(\"a.bbc.d\") == {\"a\", \"bbc\", \"d\"}\nlocal function splitByDot(str)\n  str = str or \"\"\n  local t, count = {}, 0\n  str:gsub(\"([^%.]+)\", function(c)\n    count = count + 1\n    t[count] = c\n  end)\n  return t\nend\n\nlocal function parsePrereleaseAndBuildWithSign(str)\n  local prereleaseWithSign, buildWithSign = str:match(\"^(-[^+]+)(+.+)$\")\n  if not (prereleaseWithSign and buildWithSign) then\n    prereleaseWithSign = str:match(\"^(-.+)$\")\n    buildWithSign      = str:match(\"^(+.+)$\")\n  end\n  assert(prereleaseWithSign or buildWithSign, (\"The parameter %q must begin with + or - to denote a prerelease or a build\"):format(str))\n  return prereleaseWithSign, buildWithSign\nend\n\nlocal function parsePrerelease(prereleaseWithSign)\n  if prereleaseWithSign then\n    local prerelease = prereleaseWithSign:match(\"^-(%w[%.%w-]*)$\")\n    assert(prerelease, (\"The prerelease %q is not a slash followed by alphanumerics, dots and slashes\"):format(prereleaseWithSign))\n    return prerelease\n  end\nend\n\nlocal function parseBuild(buildWithSign)\n  if buildWithSign then\n    local build = buildWithSign:match(\"^%+(%w[%.%w-]*)$\")\n    assert(build, (\"The build %q is not a + sign followed by alphanumerics, dots and slashes\"):format(buildWithSign))\n    return build\n  end\nend\n\nlocal function parsePrereleaseAndBuild(str)\n  if not present(str) then return nil, nil end\n\n  local prereleaseWithSign, buildWithSign = parsePrereleaseAndBuildWithSign(str)\n\n  local prerelease = parsePrerelease(prereleaseWithSign)\n  local build = parseBuild(buildWithSign)\n\n  return prerelease, build\nend\n\nlocal function parseVersion(str)\n  local sMajor, sMinor, sPatch, sPrereleaseAndBuild = str:match(\"^(%d+)%.?(%d*)%.?(%d*)(.-)$\")\n  assert(type(sMajor) == 'string', (\"Could not extract version number(s) from %q\"):format(str))\n  local major, minor, patch = tonumber(sMajor), tonumber(sMinor), tonumber(sPatch)\n  local prerelease, build = parsePrereleaseAndBuild(sPrereleaseAndBuild)\n  return major, minor, patch, prerelease, build\nend\n\n\n-- return 0 if a == b, -1 if a < b, and 1 if a > b\nlocal function compare(a,b)\n  return a == b and 0 or a < b and -1 or 1\nend\n\nlocal function compareIds(myId, otherId)\n  if myId == otherId then return  0\n  elseif not myId    then return -1\n  elseif not otherId then return  1\n  end\n\n  local selfNumber, otherNumber = tonumber(myId), tonumber(otherId)\n\n  if selfNumber and otherNumber then -- numerical comparison\n    return compare(selfNumber, otherNumber)\n  -- numericals are always smaller than alphanums\n  elseif selfNumber then\n    return -1\n  elseif otherNumber then\n    return 1\n  else\n    return compare(myId, otherId) -- alphanumerical comparison\n  end\nend\n\nlocal function smallerIdList(myIds, otherIds)\n  local myLength = #myIds\n  local comparison\n\n  for i=1, myLength do\n    comparison = compareIds(myIds[i], otherIds[i])\n    if comparison ~= 0 then\n      return comparison == -1\n    end\n    -- if comparison == 0, continue loop\n  end\n\n  return myLength < #otherIds\nend\n\nlocal function smallerPrerelease(mine, other)\n  if mine == other or not mine then return false\n  elseif not other then return true\n  end\n\n  return smallerIdList(splitByDot(mine), splitByDot(other))\nend\n\n---@class ISemver\nlocal methods = {}\n\n---@return Semver\nfunction methods:nextMajor()\n  return semver(self.major + 1, 0, 0)\nend\n---@return Semver\nfunction methods:nextMinor()\n  return semver(self.major, self.minor + 1, 0)\nend\n---@return Semver\nfunction methods:nextPatch()\n  return semver(self.major, self.minor, self.patch + 1)\nend\n\n---@class Semver : ISemver\n---@field major integer\n---@field minor integer\n---@field patch integer\n---@field prerelease? string\n---@field build? string\nlocal mt = { __index = methods }\nfunction mt:__eq(other)\n  return self.major == other.major and\n         self.minor == other.minor and\n         self.patch == other.patch and\n         self.prerelease == other.prerelease\n         -- notice that build is ignored for precedence in semver 2.0.0\nend\nfunction mt:__lt(other)\n  if self.major ~= other.major then return self.major < other.major end\n  if self.minor ~= other.minor then return self.minor < other.minor end\n  if self.patch ~= other.patch then return self.patch < other.patch end\n  return smallerPrerelease(self.prerelease, other.prerelease)\n  -- notice that build is ignored for precedence in semver 2.0.0\nend\n-- This works like the \"pessimisstic operator\" in Rubygems.\n-- if a and b are versions, a ^ b means \"b is backwards-compatible with a\"\n-- in other words, \"it's safe to upgrade from a to b\"\nfunction mt:__pow(other)\n  if self.major == 0 then\n    return self == other\n  end\n  return self.major == other.major and\n         self.minor <= other.minor\nend\nfunction mt:__tostring()\n  local buffer = { (\"%d.%d.%d\"):format(self.major, self.minor, self.patch) }\n  if self.prerelease then table.insert(buffer, \"-\" .. self.prerelease) end\n  if self.build      then table.insert(buffer, \"+\" .. self.build) end\n  return table.concat(buffer)\nend\n\n---@return Semver\nlocal function new(major, minor, patch, prerelease, build)\n  assert(major, \"At least one parameter is needed\")\n\n  if type(major) == 'string' then\n    major,minor,patch,prerelease,build = parseVersion(major)\n  end\n  patch = patch or 0\n  minor = minor or 0\n\n  checkPositiveInteger(major, \"major\")\n  checkPositiveInteger(minor, \"minor\")\n  checkPositiveInteger(patch, \"patch\")\n\n  local result = {major=major, minor=minor, patch=patch, prerelease=prerelease, build=build}\n  return setmetatable(result, mt)\nend\n\nsetmetatable(semver, { __call = function(_, ...) return new(...) end })\nsemver._VERSION= semver(semver._VERSION)\n\nreturn semver\n"
  },
  {
    "path": "lua/mason-vendor/zzlib/inflate-bit32.lua",
    "content": "-- stylua: ignore start\n\n-- zzlib-bit32 - zlib decompression in Lua - version using bit/bit32 libraries\n\n-- Copyright (c) 2016-2023 Francois Galea <fgalea at free.fr>\n-- This program is free software. It comes without any warranty, to\n-- the extent permitted by applicable law. You can redistribute it\n-- and/or modify it under the terms of the Do What The Fuck You Want\n-- To Public License, Version 2, as published by Sam Hocevar. See\n-- the COPYING file or http://www.wtfpl.net/ for more details.\n\n\nlocal inflate = {}\n\nlocal bit = bit32 or bit\n\ninflate.band = bit.band\ninflate.rshift = bit.rshift\n\nfunction inflate.bitstream_init(file)\n  local bs = {\n    file = file,  -- the open file handle\n    buf = nil,    -- character buffer\n    len = nil,    -- length of character buffer\n    pos = 1,      -- position in char buffer, next to be read\n    b = 0,        -- bit buffer\n    n = 0,        -- number of bits in buffer\n  }\n  -- get rid of n first bits\n  function bs:flushb(n)\n    self.n = self.n - n\n    self.b = bit.rshift(self.b,n)\n  end\n  -- returns the next byte from the stream, excluding any half-read bytes\n  function bs:next_byte()\n    if self.pos > self.len then\n      self.buf = self.file:read(4096)\n      self.len = self.buf:len()\n      self.pos = 1\n    end\n    local pos = self.pos\n    self.pos = pos + 1\n    return self.buf:byte(pos)\n  end\n  -- peek a number of n bits from stream\n  function bs:peekb(n)\n    while self.n < n do\n      self.b = self.b + bit.lshift(self:next_byte(),self.n)\n      self.n = self.n + 8\n    end\n    return bit.band(self.b,bit.lshift(1,n)-1)\n  end\n  -- get a number of n bits from stream\n  function bs:getb(n)\n    local ret = bs:peekb(n)\n    self.n = self.n - n\n    self.b = bit.rshift(self.b,n)\n    return ret\n  end\n  -- get next variable-size of maximum size=n element from stream, according to Huffman table\n  function bs:getv(hufftable,n)\n    local e = hufftable[bs:peekb(n)]\n    local len = bit.band(e,15)\n    local ret = bit.rshift(e,4)\n    self.n = self.n - len\n    self.b = bit.rshift(self.b,len)\n    return ret\n  end\n  function bs:close()\n    if self.file then\n      self.file:close()\n    end\n  end\n  if type(file) == \"string\" then\n    bs.file = nil\n    bs.buf = file\n  else\n    bs.buf = file:read(4096)\n  end\n  bs.len = bs.buf:len()\n  return bs\nend\n\nlocal function hufftable_create(depths)\n  local nvalues = #depths\n  local nbits = 1\n  local bl_count = {}\n  local next_code = {}\n  for i=1,nvalues do\n    local d = depths[i]\n    if d > nbits then\n      nbits = d\n    end\n    bl_count[d] = (bl_count[d] or 0) + 1\n  end\n  local table = {}\n  local code = 0\n  bl_count[0] = 0\n  for i=1,nbits do\n    code = (code + (bl_count[i-1] or 0)) * 2\n    next_code[i] = code\n  end\n  for i=1,nvalues do\n    local len = depths[i] or 0\n    if len > 0 then\n      local e = (i-1)*16 + len\n      local code = next_code[len]\n      local rcode = 0\n      for j=1,len do\n        rcode = rcode + bit.lshift(bit.band(1,bit.rshift(code,j-1)),len-j)\n      end\n      for j=0,2^nbits-1,2^len do\n        table[j+rcode] = e\n      end\n      next_code[len] = next_code[len] + 1\n    end\n  end\n  return table,nbits\nend\n\nlocal function block_loop(out,bs,nlit,ndist,littable,disttable)\n  local lit\n  repeat\n    lit = bs:getv(littable,nlit)\n    if lit < 256 then\n      table.insert(out,lit)\n    elseif lit > 256 then\n      local nbits = 0\n      local size = 3\n      local dist = 1\n      if lit < 265 then\n        size = size + lit - 257\n      elseif lit < 285 then\n        nbits = bit.rshift(lit-261,2)\n        size = size + bit.lshift(bit.band(lit-261,3)+4,nbits)\n      else\n        size = 258\n      end\n      if nbits > 0 then\n        size = size + bs:getb(nbits)\n      end\n      local v = bs:getv(disttable,ndist)\n      if v < 4 then\n        dist = dist + v\n      else\n        nbits = bit.rshift(v-2,1)\n        dist = dist + bit.lshift(bit.band(v,1)+2,nbits)\n        dist = dist + bs:getb(nbits)\n      end\n      local p = #out-dist+1\n      while size > 0 do\n        table.insert(out,out[p])\n        p = p + 1\n        size = size - 1\n      end\n    end\n  until lit == 256\nend\n\nlocal function block_dynamic(out,bs)\n  local order = { 17, 18, 19, 1, 9, 8, 10, 7, 11, 6, 12, 5, 13, 4, 14, 3, 15, 2, 16 }\n  local hlit = 257 + bs:getb(5)\n  local hdist = 1 + bs:getb(5)\n  local hclen = 4 + bs:getb(4)\n  local depths = {}\n  for i=1,hclen do\n    local v = bs:getb(3)\n    depths[order[i]] = v\n  end\n  for i=hclen+1,19 do\n    depths[order[i]] = 0\n  end\n  local lengthtable,nlen = hufftable_create(depths)\n  local i=1\n  while i<=hlit+hdist do\n    local v = bs:getv(lengthtable,nlen)\n    if v < 16 then\n      depths[i] = v\n      i = i + 1\n    elseif v < 19 then\n      local nbt = {2,3,7}\n      local nb = nbt[v-15]\n      local c = 0\n      local n = 3 + bs:getb(nb)\n      if v == 16 then\n        c = depths[i-1]\n      elseif v == 18 then\n        n = n + 8\n      end\n      for j=1,n do\n        depths[i] = c\n        i = i + 1\n      end\n    else\n      error(\"wrong entry in depth table for literal/length alphabet: \"..v);\n    end\n  end\n  local litdepths = {} for i=1,hlit do table.insert(litdepths,depths[i]) end\n  local littable,nlit = hufftable_create(litdepths)\n  local distdepths = {} for i=hlit+1,#depths do table.insert(distdepths,depths[i]) end\n  local disttable,ndist = hufftable_create(distdepths)\n  block_loop(out,bs,nlit,ndist,littable,disttable)\nend\n\nlocal function block_static(out,bs)\n  local cnt = { 144, 112, 24, 8 }\n  local dpt = { 8, 9, 7, 8 }\n  local depths = {}\n  for i=1,4 do\n    local d = dpt[i]\n    for j=1,cnt[i] do\n      table.insert(depths,d)\n    end\n  end\n  local littable,nlit = hufftable_create(depths)\n  depths = {}\n  for i=1,32 do\n    depths[i] = 5\n  end\n  local disttable,ndist = hufftable_create(depths)\n  block_loop(out,bs,nlit,ndist,littable,disttable)\nend\n\nlocal function block_uncompressed(out,bs)\n  bs:flushb(bit.band(bs.n,7))\n  local len = bs:getb(16)\n  if bs.n > 0 then\n    error(\"Unexpected.. should be zero remaining bits in buffer.\")\n  end\n  local nlen = bs:getb(16)\n  if bit.bxor(len,nlen) ~= 65535 then\n    error(\"LEN and NLEN don't match\")\n  end\n  for i=1,len do\n    table.insert(out,bs:next_byte())\n  end\nend\n\nfunction inflate.main(bs)\n  local last,type\n  local output = {}\n  repeat\n    local block\n    last = bs:getb(1)\n    type = bs:getb(2)\n    if type == 0 then\n      block_uncompressed(output,bs)\n    elseif type == 1 then\n      block_static(output,bs)\n    elseif type == 2 then\n      block_dynamic(output,bs)\n    else\n      error(\"unsupported block type\")\n    end\n  until last == 1\n  bs:flushb(bit.band(bs.n,7))\n  return output\nend\n\nlocal crc32_table\nfunction inflate.crc32(s,crc)\n  if not crc32_table then\n    crc32_table = {}\n    for i=0,255 do\n      local r=i\n      for j=1,8 do\n        r = bit.bxor(bit.rshift(r,1),bit.band(0xedb88320,bit.bnot(bit.band(r,1)-1)))\n      end\n      crc32_table[i] = r\n    end\n  end\n  crc = bit.bnot(crc or 0)\n  for i=1,#s do\n    local c = s:byte(i)\n    crc = bit.bxor(crc32_table[bit.bxor(c,bit.band(crc,0xff))],bit.rshift(crc,8))\n  end\n  crc = bit.bnot(crc)\n  if crc<0 then\n    -- in Lua < 5.2, sign extension was performed\n    crc = crc + 4294967296\n  end\n  return crc\nend\n\nreturn inflate\n"
  },
  {
    "path": "lua/mason-vendor/zzlib/inflate-bwo.lua",
    "content": "-- stylua: ignore start\n\n-- zzlib - zlib decompression in Lua - version using Lua 5.3 bitwise operators\n\n-- Copyright (c) 2016-2023 Francois Galea <fgalea at free.fr>\n-- This program is free software. It comes without any warranty, to\n-- the extent permitted by applicable law. You can redistribute it\n-- and/or modify it under the terms of the Do What The Fuck You Want\n-- To Public License, Version 2, as published by Sam Hocevar. See\n-- the COPYING file or http://www.wtfpl.net/ for more details.\n\n\nlocal inflate = {}\n\nfunction inflate.band(x,y) return x & y end\nfunction inflate.rshift(x,y) return x >> y end\n\nfunction inflate.bitstream_init(file)\n  local bs = {\n    file = file,  -- the open file handle\n    buf = nil,    -- character buffer\n    len = nil,    -- length of character buffer\n    pos = 1,      -- position in char buffer, next to be read\n    b = 0,        -- bit buffer\n    n = 0,        -- number of bits in buffer\n  }\n  -- get rid of n first bits\n  function bs:flushb(n)\n    self.n = self.n - n\n    self.b = self.b >> n\n  end\n  -- returns the next byte from the stream, excluding any half-read bytes\n  function bs:next_byte()\n    if self.pos > self.len then\n      self.buf = self.file:read(4096)\n      self.len = self.buf:len()\n      self.pos = 1\n    end\n    local pos = self.pos\n    self.pos = pos + 1\n    return self.buf:byte(pos)\n  end\n  -- peek a number of n bits from stream\n  function bs:peekb(n)\n    while self.n < n do\n      self.b = self.b + (self:next_byte()<<self.n)\n      self.n = self.n + 8\n    end\n    return self.b & ((1<<n)-1)\n  end\n  -- get a number of n bits from stream\n  function bs:getb(n)\n    local ret = bs:peekb(n)\n    self.n = self.n - n\n    self.b = self.b >> n\n    return ret\n  end\n  -- get next variable-size of maximum size=n element from stream, according to Huffman table\n  function bs:getv(hufftable,n)\n    local e = hufftable[bs:peekb(n)]\n    local len = e & 15\n    local ret = e >> 4\n    self.n = self.n - len\n    self.b = self.b >> len\n    return ret\n  end\n  function bs:close()\n    if self.file then\n      self.file:close()\n    end\n  end\n  if type(file) == \"string\" then\n    bs.file = nil\n    bs.buf = file\n  else\n    bs.buf = file:read(4096)\n  end\n  bs.len = bs.buf:len()\n  return bs\nend\n\nlocal function hufftable_create(depths)\n  local nvalues = #depths\n  local nbits = 1\n  local bl_count = {}\n  local next_code = {}\n  for i=1,nvalues do\n    local d = depths[i]\n    if d > nbits then\n      nbits = d\n    end\n    bl_count[d] = (bl_count[d] or 0) + 1\n  end\n  local table = {}\n  local code = 0\n  bl_count[0] = 0\n  for i=1,nbits do\n    code = (code + (bl_count[i-1] or 0)) * 2\n    next_code[i] = code\n  end\n  for i=1,nvalues do\n    local len = depths[i] or 0\n    if len > 0 then\n      local e = (i-1)*16 + len\n      local code = next_code[len]\n      local rcode = 0\n      for j=1,len do\n        rcode = rcode + ((1&(code>>(j-1))) << (len-j))\n      end\n      for j=0,2^nbits-1,2^len do\n        table[j+rcode] = e\n      end\n      next_code[len] = next_code[len] + 1\n    end\n  end\n  return table,nbits\nend\n\nlocal function block_loop(out,bs,nlit,ndist,littable,disttable)\n  local lit\n  repeat\n    lit = bs:getv(littable,nlit)\n    if lit < 256 then\n      table.insert(out,lit)\n    elseif lit > 256 then\n      local nbits = 0\n      local size = 3\n      local dist = 1\n      if lit < 265 then\n        size = size + lit - 257\n      elseif lit < 285 then\n        nbits = (lit-261) >> 2\n        size = size + ((((lit-261)&3)+4) << nbits)\n      else\n        size = 258\n      end\n      if nbits > 0 then\n        size = size + bs:getb(nbits)\n      end\n      local v = bs:getv(disttable,ndist)\n      if v < 4 then\n        dist = dist + v\n      else\n        nbits = (v-2) >> 1\n        dist = dist + (((v&1)+2) << nbits)\n        dist = dist + bs:getb(nbits)\n      end\n      local p = #out-dist+1\n      while size > 0 do\n        table.insert(out,out[p])\n        p = p + 1\n        size = size - 1\n      end\n    end\n  until lit == 256\nend\n\nlocal function block_dynamic(out,bs)\n  local order = { 17, 18, 19, 1, 9, 8, 10, 7, 11, 6, 12, 5, 13, 4, 14, 3, 15, 2, 16 }\n  local hlit = 257 + bs:getb(5)\n  local hdist = 1 + bs:getb(5)\n  local hclen = 4 + bs:getb(4)\n  local depths = {}\n  for i=1,hclen do\n    local v = bs:getb(3)\n    depths[order[i]] = v\n  end\n  for i=hclen+1,19 do\n    depths[order[i]] = 0\n  end\n  local lengthtable,nlen = hufftable_create(depths)\n  local i=1\n  while i<=hlit+hdist do\n    local v = bs:getv(lengthtable,nlen)\n    if v < 16 then\n      depths[i] = v\n      i = i + 1\n    elseif v < 19 then\n      local nbt = {2,3,7}\n      local nb = nbt[v-15]\n      local c = 0\n      local n = 3 + bs:getb(nb)\n      if v == 16 then\n        c = depths[i-1]\n      elseif v == 18 then\n        n = n + 8\n      end\n      for j=1,n do\n        depths[i] = c\n        i = i + 1\n      end\n    else\n      error(\"wrong entry in depth table for literal/length alphabet: \"..v);\n    end\n  end\n  local litdepths = {} for i=1,hlit do table.insert(litdepths,depths[i]) end\n  local littable,nlit = hufftable_create(litdepths)\n  local distdepths = {} for i=hlit+1,#depths do table.insert(distdepths,depths[i]) end\n  local disttable,ndist = hufftable_create(distdepths)\n  block_loop(out,bs,nlit,ndist,littable,disttable)\nend\n\nlocal function block_static(out,bs)\n  local cnt = { 144, 112, 24, 8 }\n  local dpt = { 8, 9, 7, 8 }\n  local depths = {}\n  for i=1,4 do\n    local d = dpt[i]\n    for j=1,cnt[i] do\n      table.insert(depths,d)\n    end\n  end\n  local littable,nlit = hufftable_create(depths)\n  depths = {}\n  for i=1,32 do\n    depths[i] = 5\n  end\n  local disttable,ndist = hufftable_create(depths)\n  block_loop(out,bs,nlit,ndist,littable,disttable)\nend\n\nlocal function block_uncompressed(out,bs)\n  bs:flushb(bs.n&7)\n  local len = bs:getb(16)\n  if bs.n > 0 then\n    error(\"Unexpected.. should be zero remaining bits in buffer.\")\n  end\n  local nlen = bs:getb(16)\n  if len~nlen ~= 65535 then\n    error(\"LEN and NLEN don't match\")\n  end\n  for i=1,len do\n    table.insert(out,bs:next_byte())\n  end\nend\n\nfunction inflate.main(bs)\n  local last,type\n  local output = {}\n  repeat\n    local block\n    last = bs:getb(1)\n    type = bs:getb(2)\n    if type == 0 then\n      block_uncompressed(output,bs)\n    elseif type == 1 then\n      block_static(output,bs)\n    elseif type == 2 then\n      block_dynamic(output,bs)\n    else\n      error(\"unsupported block type\")\n    end\n  until last == 1\n  bs:flushb(bs.n&7)\n  return output\nend\n\nlocal crc32_table\nfunction inflate.crc32(s,crc)\n  if not crc32_table then\n    crc32_table = {}\n    for i=0,255 do\n      local r=i\n      for j=1,8 do\n        r = (r >> 1) ~ (0xedb88320 & ~((r & 1) - 1))\n      end\n      crc32_table[i] = r\n    end\n  end\n  crc = (crc or 0) ~ 0xffffffff\n  for i=1,#s do\n    local c = s:byte(i)\n    crc = crc32_table[c ~ (crc & 0xff)] ~ (crc >> 8)\n  end\n  crc = (crc or 0) ~ 0xffffffff\n  if crc<0 then\n    -- in Lua < 5.2, sign extension was performed\n    crc = crc + 4294967296\n  end\n  return crc\nend\n\nreturn inflate\n"
  },
  {
    "path": "lua/mason-vendor/zzlib/init.lua",
    "content": "-- stylua: ignore start\n\n-- zzlib - zlib decompression in Lua - Implementation-independent code\n\n-- Copyright (c) 2016-2023 Francois Galea <fgalea at free.fr>\n-- This program is free software. It comes without any warranty, to\n-- the extent permitted by applicable law. You can redistribute it\n-- and/or modify it under the terms of the Do What The Fuck You Want\n-- To Public License, Version 2, as published by Sam Hocevar. See\n-- the COPYING file or http://www.wtfpl.net/ for more details.\n\n\nlocal unpack = table.unpack or unpack\nlocal infl\n\nlocal lua_version = tonumber(_VERSION:match(\"^Lua (.*)\"))\nif not lua_version or lua_version < 5.3 then\n  -- older version of Lua or Luajit being used - use bit/bit32-based implementation\n  infl = require(\"mason-vendor.zzlib.inflate-bit32\")\nelse\n  -- From Lua 5.3, use implementation based on bitwise operators\n  infl = require(\"mason-vendor.zzlib.inflate-bwo\")\nend\n\nlocal zzlib = {}\n\nlocal function arraytostr(array)\n  local tmp = {}\n  local size = #array\n  local pos = 1\n  local imax = 1\n  while size > 0 do\n    local bsize = size>=2048 and 2048 or size\n    local s = string.char(unpack(array,pos,pos+bsize-1))\n    pos = pos + bsize\n    size = size - bsize\n    local i = 1\n    while tmp[i] do\n      s = tmp[i]..s\n      tmp[i] = nil\n      i = i + 1\n    end\n    if i > imax then\n      imax = i\n    end\n    tmp[i] = s\n  end\n  local str = \"\"\n  for i=1,imax do\n    if tmp[i] then\n      str = tmp[i]..str\n    end\n  end\n  return str\nend\n\nlocal function inflate_gzip(bs)\n  local id1,id2,cm,flg = bs.buf:byte(1,4)\n  if id1 ~= 31 or id2 ~= 139 then\n    error(\"invalid gzip header\")\n  end\n  if cm ~= 8 then\n    error(\"only deflate format is supported\")\n  end\n  bs.pos=11\n  if infl.band(flg,4) ~= 0 then\n    local xl1,xl2 = bs.buf.byte(bs.pos,bs.pos+1)\n    local xlen = xl2*256+xl1\n    bs.pos = bs.pos+xlen+2\n  end\n  if infl.band(flg,8) ~= 0 then\n    local pos = bs.buf:find(\"\\0\",bs.pos)\n    bs.pos = pos+1\n  end\n  if infl.band(flg,16) ~= 0 then\n    local pos = bs.buf:find(\"\\0\",bs.pos)\n    bs.pos = pos+1\n  end\n  if infl.band(flg,2) ~= 0 then\n    -- TODO: check header CRC16\n    bs.pos = bs.pos+2\n  end\n  local result = arraytostr(infl.main(bs))\n  local crc = bs:getb(8)+256*(bs:getb(8)+256*(bs:getb(8)+256*bs:getb(8)))\n  bs:close()\n  if crc ~= infl.crc32(result) then\n    error(\"checksum verification failed\")\n  end\n  return result\nend\n\n-- compute Adler-32 checksum\nlocal function adler32(s)\n  local s1 = 1\n  local s2 = 0\n  for i=1,#s do\n    local c = s:byte(i)\n    s1 = (s1+c)%65521\n    s2 = (s2+s1)%65521\n  end\n  return s2*65536+s1\nend\n\nlocal function inflate_zlib(bs)\n  local cmf = bs.buf:byte(1)\n  local flg = bs.buf:byte(2)\n  if (cmf*256+flg)%31 ~= 0 then\n    error(\"zlib header check bits are incorrect\")\n  end\n  if infl.band(cmf,15) ~= 8 then\n    error(\"only deflate format is supported\")\n  end\n  if infl.rshift(cmf,4) ~= 7 then\n    error(\"unsupported window size\")\n  end\n  if infl.band(flg,32) ~= 0 then\n    error(\"preset dictionary not implemented\")\n  end\n  bs.pos=3\n  local result = arraytostr(infl.main(bs))\n  local adler = ((bs:getb(8)*256+bs:getb(8))*256+bs:getb(8))*256+bs:getb(8)\n  bs:close()\n  if adler ~= adler32(result) then\n    error(\"checksum verification failed\")\n  end\n  return result\nend\n\nlocal function inflate_raw(buf,offset,crc)\n  local bs = infl.bitstream_init(buf)\n  bs.pos = offset\n  local result = arraytostr(infl.main(bs))\n  if crc and crc ~= infl.crc32(result) then\n    error(\"checksum verification failed\")\n  end\n  return result\nend\n\nfunction zzlib.gunzipf(filename)\n  local file,err = io.open(filename,\"rb\")\n  if not file then\n    return nil,err\n  end\n  return inflate_gzip(infl.bitstream_init(file))\nend\n\nfunction zzlib.gunzip(str)\n  return inflate_gzip(infl.bitstream_init(str))\nend\n\nfunction zzlib.inflate(str)\n  return inflate_zlib(infl.bitstream_init(str))\nend\n\nlocal function int2le(str,pos)\n  local a,b = str:byte(pos,pos+1)\n  return b*256+a\nend\n\nlocal function int4le(str,pos)\n  local a,b,c,d = str:byte(pos,pos+3)\n  return ((d*256+c)*256+b)*256+a\nend\n\nlocal function nextfile(buf,p)\n  if int4le(buf,p) ~= 0x02014b50 then\n    -- end of central directory list\n    return\n  end\n  -- local flag = int2le(buf,p+8)\n  local packed = int2le(buf,p+10)~=0\n  local crc = int4le(buf,p+16)\n  local namelen = int2le(buf,p+28)\n  local name = buf:sub(p+46,p+45+namelen)\n  local offset = int4le(buf,p+42)+1\n  p = p+46+namelen+int2le(buf,p+30)+int2le(buf,p+32)\n  if int4le(buf,offset) ~= 0x04034b50 then\n    error(\"invalid local header signature\")\n  end\n  local size = int4le(buf,offset+18)\n  local extlen = int2le(buf,offset+28)\n  offset = offset+30+namelen+extlen\n  return p,name,offset,size,packed,crc\nend\n\nfunction zzlib.files(buf)\n  local p = #buf-21\n  if int4le(buf,p) ~= 0x06054b50 then\n    -- not sure there is a reliable way to locate the end of central directory record\n    -- if it has a variable sized comment field\n    error(\".ZIP file comments not supported\")\n  end\n  local cdoffset = int4le(buf,p+16)+1\n  return nextfile,buf,cdoffset\nend\n\nfunction zzlib.unzip(buf,arg1,arg2)\n  if type(arg1) == \"number\" then\n    -- mode 1: unpack data from specified position in zip file\n    return inflate_raw(buf,arg1,arg2)\n  end\n  -- mode 2: search and unpack file from zip file\n  local filename = arg1\n  for _,name,offset,size,packed,crc in zzlib.files(buf) do\n    if name == filename then\n      local result\n      if not packed then\n        -- no compression\n        result = buf:sub(offset,offset+size-1)\n      else\n        -- DEFLATE compression\n        result = inflate_raw(buf,offset,crc)\n      end\n      return result\n    end\n  end\n  error(\"file '\"..filename..\"' not found in ZIP archive\")\nend\n\nreturn zzlib\n"
  },
  {
    "path": "selene.toml",
    "content": "std=\"lua51+vim\"\nexclude = [\"lua/mason-vendor/*\"]\n\n[rules]\nunused_variable = \"allow\"\nshadowing = \"allow\"\nmixed_table = \"allow\"\n"
  },
  {
    "path": "stylua.toml",
    "content": "indent_type = \"Spaces\"\ncall_parentheses = \"None\"\n\n[sort_requires]\nenabled = true\n"
  },
  {
    "path": "tests/fixtures/purl-test-suite-data.json",
    "content": "[\n  {\n    \"description\": \"valid maven purl\",\n    \"purl\": \"pkg:maven/org.apache.commons/io@1.3.4\",\n    \"canonical_purl\": \"pkg:maven/org.apache.commons/io@1.3.4\",\n    \"type\": \"maven\",\n    \"namespace\": \"org.apache.commons\",\n    \"name\": \"io\",\n    \"version\": \"1.3.4\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"basic valid maven purl without version\",\n    \"purl\": \"pkg:maven/org.apache.commons/io\",\n    \"canonical_purl\": \"pkg:maven/org.apache.commons/io\",\n    \"type\": \"maven\",\n    \"namespace\": \"org.apache.commons\",\n    \"name\": \"io\",\n    \"version\": null,\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"valid go purl without version and with subpath\",\n    \"purl\": \"pkg:GOLANG/google.golang.org/genproto#/googleapis/api/annotations/\",\n    \"canonical_purl\": \"pkg:golang/google.golang.org/genproto#googleapis/api/annotations\",\n    \"type\": \"golang\",\n    \"namespace\": \"google.golang.org\",\n    \"name\": \"genproto\",\n    \"version\": null,\n    \"qualifiers\": null,\n    \"subpath\": \"googleapis/api/annotations\",\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"valid go purl with version and subpath\",\n    \"purl\": \"pkg:GOLANG/google.golang.org/genproto@abcdedf#/googleapis/api/annotations/\",\n    \"canonical_purl\": \"pkg:golang/google.golang.org/genproto@abcdedf#googleapis/api/annotations\",\n    \"type\": \"golang\",\n    \"namespace\": \"google.golang.org\",\n    \"name\": \"genproto\",\n    \"version\": \"abcdedf\",\n    \"qualifiers\": null,\n    \"subpath\": \"googleapis/api/annotations\",\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"bitbucket namespace and name should be lowercased\",\n    \"purl\": \"pkg:bitbucket/birKenfeld/pyGments-main@244fd47e07d1014f0aed9c\",\n    \"canonical_purl\": \"pkg:bitbucket/birkenfeld/pygments-main@244fd47e07d1014f0aed9c\",\n    \"type\": \"bitbucket\",\n    \"namespace\": \"birkenfeld\",\n    \"name\": \"pygments-main\",\n    \"version\": \"244fd47e07d1014f0aed9c\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"github namespace and name should be lowercased\",\n    \"purl\": \"pkg:github/Package-url/purl-Spec@244fd47e07d1004f0aed9c\",\n    \"canonical_purl\": \"pkg:github/package-url/purl-spec@244fd47e07d1004f0aed9c\",\n    \"type\": \"github\",\n    \"namespace\": \"package-url\",\n    \"name\": \"purl-spec\",\n    \"version\": \"244fd47e07d1004f0aed9c\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"debian can use qualifiers\",\n    \"purl\": \"pkg:deb/debian/curl@7.50.3-1?arch=i386&distro=jessie\",\n    \"canonical_purl\": \"pkg:deb/debian/curl@7.50.3-1?arch=i386&distro=jessie\",\n    \"type\": \"deb\",\n    \"namespace\": \"debian\",\n    \"name\": \"curl\",\n    \"version\": \"7.50.3-1\",\n    \"qualifiers\": {\"arch\": \"i386\", \"distro\": \"jessie\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"docker uses qualifiers and hash image id as versions\",\n    \"purl\": \"pkg:docker/customer/dockerimage@sha256:244fd47e07d1004f0aed9c?repository_url=gcr.io\",\n    \"canonical_purl\": \"pkg:docker/customer/dockerimage@sha256:244fd47e07d1004f0aed9c?repository_url=gcr.io\",\n    \"type\": \"docker\",\n    \"namespace\": \"customer\",\n    \"name\": \"dockerimage\",\n    \"version\": \"sha256:244fd47e07d1004f0aed9c\",\n    \"qualifiers\": {\"repository_url\": \"gcr.io\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"Java gem can use a qualifier\",\n    \"purl\": \"pkg:gem/jruby-launcher@1.1.2?Platform=java\",\n    \"canonical_purl\": \"pkg:gem/jruby-launcher@1.1.2?platform=java\",\n    \"type\": \"gem\",\n    \"namespace\": null,\n    \"name\": \"jruby-launcher\",\n    \"version\": \"1.1.2\",\n    \"qualifiers\": {\"platform\": \"java\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"maven often uses qualifiers\",\n    \"purl\": \"pkg:Maven/org.apache.xmlgraphics/batik-anim@1.9.1?classifier=sources&repositorY_url=repo.spring.io/release\",\n    \"canonical_purl\": \"pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?classifier=sources&repository_url=repo.spring.io/release\",\n    \"type\": \"maven\",\n    \"namespace\": \"org.apache.xmlgraphics\",\n    \"name\": \"batik-anim\",\n    \"version\": \"1.9.1\",\n    \"qualifiers\": {\"classifier\": \"sources\", \"repository_url\": \"repo.spring.io/release\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"maven pom reference\",\n    \"purl\": \"pkg:Maven/org.apache.xmlgraphics/batik-anim@1.9.1?extension=pom&repositorY_url=repo.spring.io/release\",\n    \"canonical_purl\": \"pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?extension=pom&repository_url=repo.spring.io/release\",\n    \"type\": \"maven\",\n    \"namespace\": \"org.apache.xmlgraphics\",\n    \"name\": \"batik-anim\",\n    \"version\": \"1.9.1\",\n    \"qualifiers\": {\"extension\": \"pom\", \"repository_url\": \"repo.spring.io/release\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"maven can come with a type qualifier\",\n    \"purl\": \"pkg:Maven/net.sf.jacob-project/jacob@1.14.3?classifier=x86&type=dll\",\n    \"canonical_purl\": \"pkg:maven/net.sf.jacob-project/jacob@1.14.3?classifier=x86&type=dll\",\n    \"type\": \"maven\",\n    \"namespace\": \"net.sf.jacob-project\",\n    \"name\": \"jacob\",\n    \"version\": \"1.14.3\",\n    \"qualifiers\": {\"classifier\": \"x86\", \"type\": \"dll\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"npm can be scoped\",\n    \"purl\": \"pkg:npm/%40angular/animation@12.3.1\",\n    \"canonical_purl\": \"pkg:npm/%40angular/animation@12.3.1\",\n    \"type\": \"npm\",\n    \"namespace\": \"@angular\",\n    \"name\": \"animation\",\n    \"version\": \"12.3.1\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"nuget names are case sensitive\",\n    \"purl\": \"pkg:Nuget/EnterpriseLibrary.Common@6.0.1304\",\n    \"canonical_purl\": \"pkg:nuget/EnterpriseLibrary.Common@6.0.1304\",\n    \"type\": \"nuget\",\n    \"namespace\": null,\n    \"name\": \"EnterpriseLibrary.Common\",\n    \"version\": \"6.0.1304\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"pypi names have special rules and not case sensitive\",\n    \"purl\": \"pkg:PYPI/Django_package@1.11.1.dev1\",\n    \"canonical_purl\": \"pkg:pypi/django-package@1.11.1.dev1\",\n    \"type\": \"pypi\",\n    \"namespace\": null,\n    \"name\": \"django-package\",\n    \"version\": \"1.11.1.dev1\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"rpm often use qualifiers\",\n    \"purl\": \"pkg:Rpm/fedora/curl@7.50.3-1.fc25?Arch=i386&Distro=fedora-25\",\n    \"canonical_purl\": \"pkg:rpm/fedora/curl@7.50.3-1.fc25?arch=i386&distro=fedora-25\",\n    \"type\": \"rpm\",\n    \"namespace\": \"fedora\",\n    \"name\": \"curl\",\n    \"version\": \"7.50.3-1.fc25\",\n    \"qualifiers\": {\"arch\": \"i386\", \"distro\": \"fedora-25\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"a scheme is always required\",\n    \"purl\": \"EnterpriseLibrary.Common@6.0.1304\",\n    \"canonical_purl\": \"EnterpriseLibrary.Common@6.0.1304\",\n    \"type\": null,\n    \"namespace\": null,\n    \"name\": \"EnterpriseLibrary.Common\",\n    \"version\": null,\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": true\n  },\n  {\n    \"description\": \"a type is always required\",\n    \"purl\": \"pkg:EnterpriseLibrary.Common@6.0.1304\",\n    \"canonical_purl\": \"pkg:EnterpriseLibrary.Common@6.0.1304\",\n    \"type\": null,\n    \"namespace\": null,\n    \"name\": \"EnterpriseLibrary.Common\",\n    \"version\": null,\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": true\n  },\n  {\n    \"description\": \"a name is required\",\n    \"purl\": \"pkg:maven/@1.3.4\",\n    \"canonical_purl\": \"pkg:maven/@1.3.4\",\n    \"type\": \"maven\",\n    \"namespace\": null,\n    \"name\": null,\n    \"version\": null,\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": true\n  },\n  {\n    \"description\": \"slash / after scheme is not significant\",\n    \"purl\": \"pkg:/maven/org.apache.commons/io\",\n    \"canonical_purl\": \"pkg:maven/org.apache.commons/io\",\n    \"type\": \"maven\",\n    \"namespace\": \"org.apache.commons\",\n    \"name\": \"io\",\n    \"version\": null,\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"double slash // after scheme is not significant\",\n    \"purl\": \"pkg://maven/org.apache.commons/io\",\n    \"canonical_purl\": \"pkg:maven/org.apache.commons/io\",\n    \"type\": \"maven\",\n    \"namespace\": \"org.apache.commons\",\n    \"name\": \"io\",\n    \"version\": null,\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"slash /// after type  is not significant\",\n    \"purl\": \"pkg:///maven/org.apache.commons/io\",\n    \"canonical_purl\": \"pkg:maven/org.apache.commons/io\",\n    \"type\": \"maven\",\n    \"namespace\": \"org.apache.commons\",\n    \"name\": \"io\",\n    \"version\": null,\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"valid maven purl with case sensitive namespace and name\",\n    \"purl\": \"pkg:maven/HTTPClient/HTTPClient@0.3-3\",\n    \"canonical_purl\": \"pkg:maven/HTTPClient/HTTPClient@0.3-3\",\n    \"type\": \"maven\",\n    \"namespace\": \"HTTPClient\",\n    \"name\": \"HTTPClient\",\n    \"version\": \"0.3-3\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"valid maven purl containing a space in the version and qualifier\",\n    \"purl\": \"pkg:maven/mygroup/myartifact@1.0.0%20Final?mykey=my%20value\",\n    \"canonical_purl\": \"pkg:maven/mygroup/myartifact@1.0.0%20Final?mykey=my%20value\",\n    \"type\": \"maven\",\n    \"namespace\": \"mygroup\",\n    \"name\": \"myartifact\",\n    \"version\": \"1.0.0 Final\",\n    \"qualifiers\": {\"mykey\": \"my value\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"checks for invalid qualifier keys\",\n    \"purl\": \"pkg:npm/myartifact@1.0.0?in%20production=true\",\n    \"canonical_purl\": null,\n    \"type\": \"npm\",\n    \"namespace\": null,\n    \"name\": \"myartifact\",\n    \"version\": \"1.0.0\",\n    \"qualifiers\": {\"in production\": \"true\"},\n    \"subpath\": null,\n    \"is_invalid\": true\n  },\n  {\n    \"description\": \"valid conan purl\",\n    \"purl\": \"pkg:conan/cctz@2.3\",\n    \"canonical_purl\": \"pkg:conan/cctz@2.3\",\n    \"type\": \"conan\",\n    \"namespace\": null,\n    \"name\": \"cctz\",\n    \"version\": \"2.3\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"valid conan purl with namespace and qualifier channel\",\n    \"purl\": \"pkg:conan/bincrafters/cctz@2.3?channel=stable\",\n    \"canonical_purl\": \"pkg:conan/bincrafters/cctz@2.3?channel=stable\",\n    \"type\": \"conan\",\n    \"namespace\": \"bincrafters\",\n    \"name\": \"cctz\",\n    \"version\": \"2.3\",\n    \"qualifiers\": {\"channel\": \"stable\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"invalid conan purl only namespace\",\n    \"purl\": \"pkg:conan/bincrafters/cctz@2.3\",\n    \"canonical_purl\": \"pkg:conan/bincrafters/cctz@2.3\",\n    \"type\": \"conan\",\n    \"namespace\": \"bincrafters\",\n    \"name\": \"cctz\",\n    \"version\": \"2.3\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": true\n  },\n  {\n    \"description\": \"invalid conan purl only channel qualifier\",\n    \"purl\": \"pkg:conan/cctz@2.3?channel=stable\",\n    \"canonical_purl\": \"pkg:conan/cctz@2.3?channel=stable\",\n    \"type\": \"conan\",\n    \"namespace\": null,\n    \"name\": \"cctz\",\n    \"version\": \"2.3\",\n    \"qualifiers\": {\"channel\": \"stable\"},\n    \"subpath\": null,\n    \"is_invalid\": true\n  },\n  {\n    \"description\": \"valid conda purl with qualifiers\",\n    \"purl\": \"pkg:conda/absl-py@0.4.1?build=py36h06a4308_0&channel=main&subdir=linux-64&type=tar.bz2\",\n    \"canonical_purl\": \"pkg:conda/absl-py@0.4.1?build=py36h06a4308_0&channel=main&subdir=linux-64&type=tar.bz2\",\n    \"type\": \"conda\",\n    \"namespace\": null,\n    \"name\": \"absl-py\",\n    \"version\": \"0.4.1\",\n    \"qualifiers\": {\"build\": \"py36h06a4308_0\", \"channel\": \"main\", \"subdir\": \"linux-64\", \"type\": \"tar.bz2\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"valid cran purl\",\n    \"purl\": \"pkg:cran/A3@0.9.1\",\n    \"canonical_purl\": \"pkg:cran/A3@0.9.1\",\n    \"type\": \"cran\",\n    \"namespace\": null,\n    \"name\": \"A3\",\n    \"version\": \"0.9.1\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"invalid cran purl without name\",\n    \"purl\": \"pkg:cran/@0.9.1\",\n    \"canonical_purl\": \"pkg:cran/@0.9.1\",\n    \"type\": \"cran\",\n    \"namespace\": null,\n    \"name\": null,\n    \"version\": \"0.9.1\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": true\n  },\n  {\n    \"description\": \"invalid cran purl without version\",\n    \"purl\": \"pkg:cran/A3\",\n    \"canonical_purl\": \"pkg:cran/A3\",\n    \"type\": \"cran\",\n    \"namespace\": null,\n    \"name\": \"A3\",\n    \"version\": null,\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": true\n  },\n  {\n    \"description\": \"valid swift purl\",\n    \"purl\": \"pkg:swift/github.com/Alamofire/Alamofire@5.4.3\",\n    \"canonical_purl\": \"pkg:swift/github.com/Alamofire/Alamofire@5.4.3\",\n    \"type\": \"swift\",\n    \"namespace\": \"github.com/Alamofire\",\n    \"name\": \"Alamofire\",\n    \"version\": \"5.4.3\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"invalid swift purl without namespace\",\n    \"purl\": \"pkg:swift/Alamofire@5.4.3\",\n    \"canonical_purl\": \"pkg:swift/Alamofire@5.4.3\",\n    \"type\": \"swift\",\n    \"namespace\": null,\n    \"name\": \"Alamofire\",\n    \"version\": \"5.4.3\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": true\n  },\n  {\n    \"description\": \"invalid swift purl without name\",\n    \"purl\": \"pkg:swift/github.com/Alamofire/@5.4.3\",\n    \"canonical_purl\": \"pkg:swift/github.com/Alamofire/@5.4.3\",\n    \"type\": \"swift\",\n    \"namespace\": \"github.com/Alamofire\",\n    \"name\": null,\n    \"version\": \"5.4.3\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": true\n  },\n  {\n    \"description\": \"invalid swift purl without version\",\n    \"purl\": \"pkg:swift/github.com/Alamofire/Alamofire\",\n    \"canonical_purl\": \"pkg:swift/github.com/Alamofire/Alamofire\",\n    \"type\": \"swift\",\n    \"namespace\": \"github.com/Alamofire\",\n    \"name\": \"Alamofire\",\n    \"version\": null,\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": true\n  },\n  {\n    \"description\": \"valid hackage purl\",\n    \"purl\": \"pkg:hackage/AC-HalfInteger@1.2.1\",\n    \"canonical_purl\": \"pkg:hackage/AC-HalfInteger@1.2.1\",\n    \"type\": \"hackage\",\n    \"namespace\": null,\n    \"name\": \"AC-HalfInteger\",\n    \"version\": \"1.2.1\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"name and version are always required\",\n    \"purl\": \"pkg:hackage\",\n    \"canonical_purl\": \"pkg:hackage\",\n    \"type\": \"hackage\",\n    \"namespace\": null,\n    \"name\": null,\n    \"version\": null,\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": true\n  },\n  {\n    \"description\": \"minimal Hugging Face model\",\n    \"purl\": \"pkg:huggingface/distilbert-base-uncased@043235d6088ecd3dd5fb5ca3592b6913fd516027\",\n    \"canonical_purl\": \"pkg:huggingface/distilbert-base-uncased@043235d6088ecd3dd5fb5ca3592b6913fd516027\",\n    \"type\": \"huggingface\",\n    \"namespace\": null,\n    \"name\": \"distilbert-base-uncased\",\n    \"version\": \"043235d6088ecd3dd5fb5ca3592b6913fd516027\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"Hugging Face model with staging endpoint\",\n    \"purl\": \"pkg:huggingface/microsoft/deberta-v3-base@559062ad13d311b87b2c455e67dcd5f1c8f65111?repository_url=https://hub-ci.huggingface.co\",\n    \"canonical_purl\": \"pkg:huggingface/microsoft/deberta-v3-base@559062ad13d311b87b2c455e67dcd5f1c8f65111?repository_url=https://hub-ci.huggingface.co\",\n    \"type\": \"huggingface\",\n    \"namespace\": \"microsoft\",\n    \"name\": \"deberta-v3-base\",\n    \"version\": \"559062ad13d311b87b2c455e67dcd5f1c8f65111\",\n    \"qualifiers\": {\"repository_url\": \"https://hub-ci.huggingface.co\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"Hugging Face model with various cases\",\n    \"purl\": \"pkg:huggingface/EleutherAI/gpt-neo-1.3B@797174552AE47F449AB70B684CABCB6603E5E85E\",\n    \"canonical_purl\": \"pkg:huggingface/EleutherAI/gpt-neo-1.3B@797174552ae47f449ab70b684cabcb6603e5e85e\",\n    \"type\": \"huggingface\",\n    \"namespace\": \"EleutherAI\",\n    \"name\": \"gpt-neo-1.3B\",\n    \"version\": \"797174552ae47f449ab70b684cabcb6603e5e85e\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"MLflow model tracked in Azure Databricks (case insensitive)\",\n    \"purl\": \"pkg:mlflow/CreditFraud@3?repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow\",\n    \"canonical_purl\": \"pkg:mlflow/creditfraud@3?repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow\",\n    \"type\": \"mlflow\",\n    \"namespace\": null,\n    \"name\": \"creditfraud\",\n    \"version\": \"3\",\n    \"qualifiers\": {\"repository_url\": \"https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"MLflow model tracked in Azure ML (case sensitive)\",\n    \"purl\": \"pkg:mlflow/CreditFraud@3?repository_url=https://westus2.api.azureml.ms/mlflow/v1.0/subscriptions/a50f2011-fab8-4164-af23-c62881ef8c95/resourceGroups/TestResourceGroup/providers/Microsoft.MachineLearningServices/workspaces/TestWorkspace\",\n    \"canonical_purl\": \"pkg:mlflow/CreditFraud@3?repository_url=https://westus2.api.azureml.ms/mlflow/v1.0/subscriptions/a50f2011-fab8-4164-af23-c62881ef8c95/resourceGroups/TestResourceGroup/providers/Microsoft.MachineLearningServices/workspaces/TestWorkspace\",\n    \"type\": \"mlflow\",\n    \"namespace\": null,\n    \"name\": \"CreditFraud\",\n    \"version\": \"3\",\n    \"qualifiers\": {\"repository_url\": \"https://westus2.api.azureml.ms/mlflow/v1.0/subscriptions/a50f2011-fab8-4164-af23-c62881ef8c95/resourceGroups/TestResourceGroup/providers/Microsoft.MachineLearningServices/workspaces/TestWorkspace\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"MLflow model with unique identifiers\",\n    \"purl\": \"pkg:mlflow/trafficsigns@10?model_uuid=36233173b22f4c89b451f1228d700d49&run_id=410a3121-2709-4f88-98dd-dba0ef056b0a&repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow\",\n    \"canonical_purl\": \"pkg:mlflow/trafficsigns@10?model_uuid=36233173b22f4c89b451f1228d700d49&repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow&run_id=410a3121-2709-4f88-98dd-dba0ef056b0a\",\n    \"type\": \"mlflow\",\n    \"namespace\": null,\n    \"name\": \"trafficsigns\",\n    \"version\": \"10\",\n    \"qualifiers\": {\"model_uuid\": \"36233173b22f4c89b451f1228d700d49\", \"run_id\": \"410a3121-2709-4f88-98dd-dba0ef056b0a\", \"repository_url\": \"https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow\"},\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"composer names are not case sensitive\",\n    \"purl\": \"pkg:composer/Laravel/Laravel@5.5.0\",\n    \"canonical_purl\": \"pkg:composer/laravel/laravel@5.5.0\",\n    \"type\": \"composer\",\n    \"namespace\": \"laravel\",\n    \"name\": \"laravel\",\n    \"version\": \"5.5.0\",\n    \"qualifiers\": null,\n    \"subpath\": null,\n    \"is_invalid\": false\n  },\n  {\n    \"description\": \"splits checksum qualifier\",\n    \"purl\": \"pkg:github/rust-lang/rust-analyzer@2022-12-05?download_url=https://github.com/rust-lang/rust-analyzer/releases/download/2022-12-05/rust-analyzer-aarch64-apple-darwin.gz&checksum=sha1:256d83a0a59929099e7564169ef444c5e4088afc,sha256:28461b29ac0da9c653616e1d96092c85f86e24dd448d0fbe1973aa4c6d9b8b44\",\n    \"canonical_purl\": \"pkg:github/rust-lang/rust-analyzer@2022-12-05?checksum=sha1:256d83a0a59929099e7564169ef444c5e4088afc,sha256:28461b29ac0da9c653616e1d96092c85f86e24dd448d0fbe1973aa4c6d9b8b44&download_url=https://github.com/rust-lang/rust-analyzer/releases/download/2022-12-05/rust-analyzer-aarch64-apple-darwin.gz\",\n    \"type\": \"github\",\n    \"namespace\": \"rust-lang\",\n    \"name\": \"rust-analyzer\",\n    \"version\": \"2022-12-05\",\n    \"qualifiers\": {\"download_url\": \"https://github.com/rust-lang/rust-analyzer/releases/download/2022-12-05/rust-analyzer-aarch64-apple-darwin.gz\", \"checksum\": [\"sha1:256d83a0a59929099e7564169ef444c5e4088afc\", \"sha256:28461b29ac0da9c653616e1d96092c85f86e24dd448d0fbe1973aa4c6d9b8b44\"]},\n    \"subpath\": null,\n    \"is_invalid\": false\n  }\n]\n"
  },
  {
    "path": "tests/fixtures/receipts/1.0.json",
    "content": "{\n  \"schema_version\": \"1.0\",\n  \"primary_source\": {\n    \"type\": \"npm\",\n    \"package\": \"@angular/language-server\"\n  },\n  \"links\": {\n    \"bin\": {\n      \"ngserver\": \"node_modules/.bin/ngserver\"\n    }\n  },\n  \"metrics\": {\n    \"start_time\": 1694752057715,\n    \"completion_time\": 1694752066467\n  },\n  \"secondary_sources\": [\n    {\n      \"type\": \"npm\",\n      \"package\": \"typescript\"\n    }\n  ],\n  \"name\": \"angular-language-server\"\n}\n"
  },
  {
    "path": "tests/fixtures/receipts/1.1.json",
    "content": "{\n  \"schema_version\": \"1.1\",\n  \"metrics\": {\n    \"start_time\": 1694752380220,\n    \"completion_time\": 1694752386830\n  },\n  \"links\": {\n    \"share\": {},\n    \"opt\": {},\n    \"bin\": {\n      \"ngserver\": \"node_modules/.bin/ngserver\"\n    }\n  },\n  \"name\": \"angular-language-server\",\n  \"primary_source\": {\n    \"type\": \"registry+v1\",\n    \"id\": \"pkg:npm/%40angular/language-server@16.1.8\",\n    \"source\": {\n      \"extra_packages\": [\n        \"typescript@5.1.3\"\n      ],\n      \"version\": \"16.1.8\",\n      \"package\": \"@angular/language-server\"\n    }\n  },\n  \"secondary_sources\": []\n}\n"
  },
  {
    "path": "tests/fixtures/receipts/2.0.json",
    "content": "{\n  \"links\": {\n    \"bin\": {\n      \"ngserver\": \"node_modules/.bin/ngserver\"\n    },\n    \"share\": {},\n    \"opt\": {}\n  },\n  \"name\": \"angular-language-server\",\n  \"schema_version\": \"2.0\",\n  \"metrics\": {\n    \"start_time\": 1739692587948,\n    \"completion_time\": 1739692591360\n  },\n  \"source\": {\n    \"id\": \"pkg:npm/%40angular/language-server@19.1.0\",\n    \"raw\": {\n      \"id\": \"pkg:npm/%40angular/language-server@19.1.0\",\n      \"extra_packages\": [\n        \"typescript@5.4.5\"\n      ]\n    },\n    \"type\": \"registry+v1\"\n  },\n  \"install_options\": {\n    \"debug\": false,\n    \"strict\": false,\n    \"force\": false\n  },\n  \"registry\": {\n    \"name\": \"mason-registry\",\n    \"version\": \"2025-05-03-lawful-clave\",\n    \"checksums\": {\n      \"registry.json\": \"4ae083fe8e50d0bea5382be05c7ede8d2def55ff2b6b89dc129b153039d9f2a2\",\n      \"registry.json.zip\": \"2116d5db7676afe7052de329db4dfbf656054d8c35ce12414eb9d58561b2fde9\"\n    },\n    \"proto\": \"github\",\n    \"namespace\": \"mason-org\"\n  }\n}\n"
  },
  {
    "path": "tests/helpers/lua/dummy-registry/dummy.lua",
    "content": "return {\n    name = \"dummy\",\n    description = [[This is a dummy package.]],\n    homepage = \"https://example.com\",\n    licenses = { \"MIT\" },\n    languages = { \"DummyLang\" },\n    categories = { \"LSP\" },\n    source = {\n        id = \"pkg:mason/dummy@1.0.0\",\n        ---@async\n        ---@param ctx InstallContext\n        install = function(ctx) end,\n    },\n}\n"
  },
  {
    "path": "tests/helpers/lua/dummy-registry/dummy2.lua",
    "content": "return {\n    name = \"dummy2\",\n    description = [[This is a dummy2 package.]],\n    homepage = \"https://example.com\",\n    licenses = { \"MIT\" },\n    languages = { \"Dummy2Lang\" },\n    categories = { \"LSP\" },\n    source = {\n        id = \"pkg:mason/dummy2@1.0.0\",\n        ---@async\n        ---@param ctx InstallContext\n        install = function(ctx) end,\n    },\n}\n"
  },
  {
    "path": "tests/helpers/lua/dummy-registry/index.lua",
    "content": "return {\n    [\"dummy\"] = \"dummy-registry.dummy\",\n    [\"dummy2\"] = \"dummy-registry.dummy2\",\n    [\"registry\"] = \"dummy-registry.registry\",\n}\n"
  },
  {
    "path": "tests/helpers/lua/dummy-registry/registry.lua",
    "content": "return {\n    name = \"registry\",\n    description = [[This is a dummy package.]],\n    homepage = \"https://example.com\",\n    licenses = { \"MIT\" },\n    languages = { \"DummyLang\" },\n    categories = { \"LSP\" },\n    source = {\n        id = \"pkg:dummy/registry@1.0.0\",\n    },\n}\n"
  },
  {
    "path": "tests/helpers/lua/luassertx.lua",
    "content": "local a = require \"mason-core.async\"\nlocal assert = require \"luassert\"\nlocal match = require \"luassert.match\"\n\nlocal function wait(_, arguments)\n    ---@type (fun()) Function to execute until it does not error.\n    local assertions_fn = arguments[1]\n    ---@type number Timeout in milliseconds. Defaults to 5000.\n    local timeout = arguments[2] or 5000\n\n    local err\n    if\n        not vim.wait(timeout, function()\n            local ok, err_ = pcall(assertions_fn)\n            err = err_\n            return ok\n        end, math.min(timeout, 100))\n    then\n        error(err)\n    end\n\n    return true\nend\n\nlocal function wait_for(_, arguments)\n    ---@type (fun()) Function to execute until it does not error.\n    local assertions_fn = arguments[1]\n    ---@type number Timeout in milliseconds. Defaults to 5000.\n    local timeout = arguments[2]\n    timeout = timeout or 15000\n\n    local start = vim.loop.hrtime()\n    local is_ok, err\n    repeat\n        is_ok, err = pcall(assertions_fn)\n        if not is_ok then\n            a.sleep(math.min(timeout, 100))\n        end\n    until is_ok or ((vim.loop.hrtime() - start) / 1e6) > timeout\n\n    if not is_ok then\n        error(err)\n    end\n\n    return is_ok\nend\n\nlocal function tbl_containing(_, arguments, _)\n    return function(value)\n        local expected = arguments[1]\n        for key, val in pairs(expected) do\n            if match.is_matcher(val) then\n                if not val(value[key]) then\n                    return false\n                end\n            elseif value[key] ~= val then\n                return false\n            end\n        end\n        return true\n    end\nend\n\nlocal function list_containing(_, arguments, _)\n    return function(value)\n        local expected = arguments[1]\n        for _, val in pairs(value) do\n            if match.is_matcher(expected) then\n                if expected(val) then\n                    return true\n                end\n            elseif expected == val then\n                return true\n            end\n        end\n        return false\n    end\nend\n\nlocal function instanceof(_, arguments, _)\n    return function(value)\n        local expected_mt = arguments[1]\n        return getmetatable(value) == expected_mt\n    end\nend\n\nlocal function capture(_, arguments, _)\n    return function(value)\n        arguments[1](value)\n        return true\n    end\nend\n\nassert:register(\"matcher\", \"tbl_containing\", tbl_containing)\nassert:register(\"matcher\", \"list_containing\", list_containing)\nassert:register(\"matcher\", \"instanceof\", instanceof)\nassert:register(\"matcher\", \"capture\", capture)\nassert:register(\"assertion\", \"wait_for\", wait_for)\nassert:register(\"assertion\", \"wait\", wait)\n"
  },
  {
    "path": "tests/mason/api/command_spec.lua",
    "content": "local log = require \"mason-core.log\"\nlocal match = require \"luassert.match\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\n\nlocal Pkg = require \"mason-core.package\"\nlocal a = require \"mason-core.async\"\nlocal api = require \"mason.api.command\"\nlocal registry = require \"mason-registry\"\n\ndescribe(\":Mason\", function()\n    it(\"should open the UI window\", function()\n        api.Mason()\n        a.run_blocking(a.wait, vim.schedule)\n        local win = vim.api.nvim_get_current_win()\n        local buf = vim.api.nvim_win_get_buf(win)\n        assert.equals(\"mason\", vim.api.nvim_buf_get_option(buf, \"filetype\"))\n    end)\nend)\n\ndescribe(\":MasonInstall\", function()\n    it(\"should install the provided packages\", function()\n        local dummy = registry.get_package \"dummy\"\n        local dummy2 = registry.get_package \"dummy2\"\n        spy.on(Pkg, \"install\")\n        api.MasonInstall { \"dummy@1.0.0\", \"dummy2\" }\n        assert.spy(Pkg.install).was_called(2)\n        assert.spy(Pkg.install).was_called_with(match.is_ref(dummy), { version = \"1.0.0\" }, match.is_function())\n        assert\n            .spy(Pkg.install)\n            .was_called_with(match.is_ref(dummy2), match.tbl_containing { version = match.is_nil() }, match.is_function())\n    end)\n\n    it(\"should install provided packages in debug mode\", function()\n        local dummy = registry.get_package \"dummy\"\n        local dummy2 = registry.get_package \"dummy2\"\n        spy.on(Pkg, \"install\")\n        vim.cmd [[MasonInstall --debug dummy dummy2]]\n        assert.spy(Pkg.install).was_called(2)\n        assert\n            .spy(Pkg.install)\n            .was_called_with(match.is_ref(dummy), { version = nil, debug = true }, match.is_function())\n        assert\n            .spy(Pkg.install)\n            .was_called_with(match.is_ref(dummy2), { version = nil, debug = true }, match.is_function())\n    end)\n\n    it(\"should open the UI window\", function()\n        local dummy = registry.get_package \"dummy\"\n        spy.on(dummy, \"install\")\n        api.MasonInstall { \"dummy\" }\n        local win = vim.api.nvim_get_current_win()\n        local buf = vim.api.nvim_win_get_buf(win)\n        assert.equals(\"mason\", vim.api.nvim_buf_get_option(buf, \"filetype\"))\n    end)\nend)\n\ndescribe(\":MasonUninstall\", function()\n    it(\"should uninstall the provided packages\", function()\n        local dummy = registry.get_package \"dummy\"\n        local dummy2 = registry.get_package \"dummy\"\n        spy.on(Pkg, \"uninstall\")\n        api.MasonUninstall { \"dummy\", \"dummy2\" }\n        assert.spy(Pkg.uninstall).was_called(2)\n        assert.spy(Pkg.uninstall).was_called_with(match.is_ref(dummy))\n        assert.spy(Pkg.uninstall).was_called_with(match.is_ref(dummy2))\n    end)\nend)\n\ndescribe(\":MasonLog\", function()\n    it(\"should open the log file\", function()\n        api.MasonLog()\n        assert.equals(2, #vim.api.nvim_list_tabpages())\n        local win = vim.api.nvim_get_current_win()\n        local buf = vim.api.nvim_win_get_buf(win)\n        vim.api.nvim_buf_call(buf, function()\n            assert.equals(log.outfile, vim.fn.expand \"%\")\n        end)\n    end)\nend)\n\ndescribe(\":MasonUpdate\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should update registries\", function()\n        stub(registry, \"update\", function(cb)\n            cb(true, { {} })\n        end)\n        spy.on(vim, \"notify\")\n        api.MasonUpdate()\n        assert.spy(vim.notify).was_called(2)\n        assert.spy(vim.notify).was_called_with(\"Updating registries…\", vim.log.levels.INFO, {\n            title = \"mason.nvim\",\n        })\n        assert.spy(vim.notify).was_called_with(\"Successfully updated 1 registry.\", vim.log.levels.INFO, {\n            title = \"mason.nvim\",\n        })\n    end)\n\n    it(\"should notify errors\", function()\n        stub(registry, \"update\", function(cb)\n            cb(false, \"Some error.\")\n        end)\n        spy.on(vim, \"notify\")\n        api.MasonUpdate()\n        assert.spy(vim.notify).was_called(2)\n        assert.spy(vim.notify).was_called_with(\"Updating registries…\", vim.log.levels.INFO, {\n            title = \"mason.nvim\",\n        })\n        assert.spy(vim.notify).was_called_with(\"Failed to update registries: Some error.\", vim.log.levels.ERROR, {\n            title = \"mason.nvim\",\n        })\n    end)\nend)\n"
  },
  {
    "path": "tests/mason/setup_spec.lua",
    "content": "local InstallLocation = require \"mason-core.installer.InstallLocation\"\nlocal mason = require \"mason\"\nlocal match = require \"luassert.match\"\nlocal settings = require \"mason.settings\"\n\ndescribe(\"mason setup\", function()\n    before_each(function()\n        vim.env.MASON = nil\n        vim.env.PATH = \"/usr/local/bin:/usr/bin\"\n        settings.set(settings._DEFAULT_SETTINGS)\n    end)\n\n    it(\"should modify the PATH environment\", function()\n        mason.setup()\n        local global_location = InstallLocation.global()\n        assert.equals((\"%s:/usr/local/bin:/usr/bin\"):format(global_location:bin()), vim.env.PATH)\n    end)\n\n    it(\"should prepend the PATH environment\", function()\n        mason.setup { PATH = \"prepend\" }\n        local global_location = InstallLocation.global()\n        assert.equals((\"%s:/usr/local/bin:/usr/bin\"):format(global_location:bin()), vim.env.PATH)\n    end)\n\n    it(\"should append PATH\", function()\n        mason.setup { PATH = \"append\" }\n        local global_location = InstallLocation.global()\n        assert.equals((\"/usr/local/bin:/usr/bin:%s\"):format(global_location:bin()), vim.env.PATH)\n    end)\n\n    it(\"shouldn't modify PATH\", function()\n        local PATH = vim.env.PATH\n        local global_location = InstallLocation.global()\n        mason.setup { PATH = \"skip\" }\n        assert.equals(PATH, vim.env.PATH)\n    end)\n\n    it(\"should set MASON env\", function()\n        assert.is_nil(vim.env.MASON)\n        local global_location = InstallLocation.global()\n        mason.setup()\n        assert.equals(vim.fn.expand \"~/.local/share/nvim/mason\", vim.env.MASON)\n    end)\n\n    it(\"should set up user commands\", function()\n        local global_location = InstallLocation.global()\n        mason.setup()\n        local user_commands = vim.api.nvim_get_commands {}\n\n        assert.is_true(match.tbl_containing {\n            bang = false,\n            bar = false,\n            nargs = \"0\",\n            definition = \"Opens mason's UI window.\",\n        }(user_commands[\"Mason\"]))\n\n        assert.is_true(match.tbl_containing {\n            bang = false,\n            bar = false,\n            definition = \"Install one or more packages.\",\n            nargs = \"+\",\n            complete = \"<Lua function>\",\n        }(user_commands[\"MasonInstall\"]))\n\n        assert.is_true(match.tbl_containing {\n            bang = false,\n            bar = false,\n            definition = \"Uninstall one or more packages.\",\n            nargs = \"+\",\n            complete = \"<Lua function>\",\n        }(user_commands[\"MasonUninstall\"]))\n\n        assert.is_true(match.tbl_containing {\n            bang = false,\n            bar = false,\n            definition = \"Uninstall all packages.\",\n            nargs = \"0\",\n        }(user_commands[\"MasonUninstallAll\"]))\n\n        assert.is_true(match.tbl_containing {\n            bang = false,\n            bar = false,\n            definition = \"Opens the mason.nvim log.\",\n            nargs = \"0\",\n        }(user_commands[\"MasonLog\"]))\n    end)\n\n    it(\"should set the has_setup flag\", function()\n        package.loaded[\"mason\"] = nil\n        local mason = require \"mason\"\n        assert.is_false(mason.has_setup)\n        mason.setup()\n        assert.is_true(mason.has_setup)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/EventEmitter_spec.lua",
    "content": "local log = require \"mason-core.log\"\nlocal match = require \"luassert.match\"\nlocal spy = require \"luassert.spy\"\n\nlocal EventEmitter = require \"mason-core.EventEmitter\"\nlocal a = require \"mason-core.async\"\n\ndescribe(\"EventEmitter\", function()\n    it(\"should call registered event handlers\", function()\n        local emitter = EventEmitter.init(setmetatable({}, { __index = EventEmitter }))\n        local my_event_handler = spy.new()\n        emitter:on(\"my:event\", my_event_handler --[[@as fun()]])\n\n        emitter:emit(\"my:event\", { table = \"value\" })\n        emitter:emit(\"my:event\", 1337, 42)\n\n        assert.spy(my_event_handler).was_called(2)\n        assert.spy(my_event_handler).was_called_with(match.same { table = \"value\" })\n        assert.spy(my_event_handler).was_called_with(1337, 42)\n    end)\n\n    it(\"should call registered event handlers only once\", function()\n        local emitter = EventEmitter.init(setmetatable({}, { __index = EventEmitter }))\n        local my_event_handler = spy.new()\n        emitter:once(\"my:event\", my_event_handler --[[@as fun()]])\n\n        emitter:emit(\"my:event\", { table = \"value\" })\n        emitter:emit(\"my:event\", 1337, 42)\n\n        assert.spy(my_event_handler).was_called(1)\n        assert.spy(my_event_handler).was_called_with(match.same { table = \"value\" })\n    end)\n\n    it(\"should remove registered event handlers\", function()\n        local emitter = EventEmitter.init(setmetatable({}, { __index = EventEmitter }))\n        local my_event_handler = spy.new() --[[@as fun()]]\n        emitter:on(\"my:event\", my_event_handler)\n        emitter:once(\"my:event\", my_event_handler)\n\n        emitter:off(\"my:event\", my_event_handler)\n\n        emitter:emit(\"my:event\", { table = \"value\" })\n        assert.spy(my_event_handler).was_called(0)\n    end)\n\n    it(\"should log errors in handlers\", function()\n        spy.on(log, \"fmt_warn\")\n        local emitter = EventEmitter.init(setmetatable({}, { __index = EventEmitter }))\n        emitter:on(\"event\", mockx.throws \"My error.\")\n        emitter:emit \"event\"\n        a.run_blocking(a.wait, vim.schedule)\n        assert.spy(log.fmt_warn).was_called(1)\n        assert\n            .spy(log.fmt_warn)\n            .was_called_with(\"EventEmitter handler failed for event %s with error %s\", \"event\", \"My error.\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/async/async_spec.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal assert = require \"luassert\"\nlocal control = require \"mason-core.async.control\"\nlocal match = require \"luassert.match\"\nlocal process = require \"mason-core.process\"\nlocal spy = require \"luassert.spy\"\n\nlocal function timestamp()\n    local seconds, microseconds = vim.loop.gettimeofday()\n    return (seconds * 1000) + math.floor(microseconds / 1000)\nend\n\ndescribe(\"async\", function()\n    it(\"should run in blocking mode\", function()\n        local start = timestamp()\n        a.run_blocking(function()\n            a.sleep(100)\n        end)\n        local stop = timestamp()\n        local grace_ms = 50\n        assert.is_true((stop - start) >= (100 - grace_ms))\n    end)\n\n    it(\"should return values in blocking mode\", function()\n        local function slow_maths(arg1, arg2)\n            a.sleep(10)\n            return arg1 + arg2 - 42\n        end\n        local value = a.run_blocking(slow_maths, 13, 37)\n        assert.equals(8, value)\n    end)\n\n    it(\"should pass arguments to .run\", function()\n        local fn = spy.new()\n        a.run(function(...)\n            fn(...)\n        end, spy.new(), 100, 200)\n        assert.spy(fn).was_called(1)\n        assert.spy(fn).was_called_with(100, 200)\n    end)\n\n    it(\"should wrap callback-style async functions via promisify\", function()\n        local async_spawn = _.compose(_.table_pack, a.promisify(process.spawn))\n        local stdio = process.BufferedSink:new()\n        local success, exit_code = unpack(a.run_blocking(async_spawn, \"env\", {\n            args = {},\n            env = { \"FOO=BAR\", \"BAR=BAZ\" },\n            stdio_sink = stdio,\n        }))\n        assert.is_true(success)\n        assert.equals(0, exit_code)\n        assert.equals(\"FOO=BAR\\nBAR=BAZ\\n\", table.concat(stdio.buffers.stdout, \"\"))\n    end)\n\n    it(\"should propagate errors in callback-style functions via promisify\", function()\n        local err = assert.has_error(function()\n            a.run_blocking(a.promisify(function(cb)\n                cb \"Error message.\"\n            end, true))\n        end)\n        assert.equals(err, \"Error message.\")\n    end)\n\n    it(\"should return all values from a.wait\", function()\n        a.run_blocking(function()\n            local val1, val2, val3 = a.wait(function(resolve)\n                resolve(1, 2, 3)\n            end)\n            assert.equals(1, val1)\n            assert.equals(2, val2)\n            assert.equals(3, val3)\n        end)\n    end)\n\n    it(\"should cancel coroutine\", function()\n        local capture = spy.new()\n        a.run_blocking(function()\n            local cancel = a.scope(function()\n                a.sleep(10)\n                capture()\n            end)()\n            cancel()\n            a.sleep(20)\n        end)\n        assert.spy(capture).was_not.called()\n    end)\n\n    it(\"should raise error if async function raises error\", function()\n        a.run_blocking(function()\n            local err = assert.has.errors(a.promisify(function()\n                error \"something went wrong\"\n            end))\n            assert.is_true(match.has_match \"something went wrong$\"(err))\n        end)\n    end)\n\n    it(\"should raise error if async function rejects\", function()\n        a.run_blocking(function()\n            local err = assert.has.errors(function()\n                a.wait(function(_, reject)\n                    reject \"This is an error\"\n                end)\n            end)\n            assert.equals(\"This is an error\", err)\n        end)\n    end)\n\n    it(\"should pass nil arguments to promisified functions\", function()\n        local fn = spy.new(function(_, _, _, _, _, _, _, cb)\n            cb()\n        end)\n        a.run_blocking(function()\n            a.promisify(fn)(nil, 2, nil, 4, nil, nil, 7)\n        end)\n        assert.spy(fn).was_called_with(nil, 2, nil, 4, nil, nil, 7, match.is_function())\n    end)\n\n    it(\"should accept yielding non-promise values to parent coroutine context\", function()\n        local thread = coroutine.create(function(val)\n            a.run_blocking(function()\n                coroutine.yield(val)\n            end)\n        end)\n        local ok, value = coroutine.resume(thread, 1337)\n        assert.is_true(ok)\n        assert.equals(1337, value)\n    end)\n\n    it(\"should run all suspending functions concurrently\", function()\n        local function sleep(ms, ret_val)\n            return function()\n                a.sleep(ms)\n                return ret_val\n            end\n        end\n        local start = timestamp()\n        local one, two, three, four, five = unpack(a.run_blocking(function()\n            return _.table_pack(a.wait_all {\n                sleep(100, 1),\n                sleep(100, \"two\"),\n                sleep(100, \"three\"),\n                sleep(100, 4),\n                sleep(100, 5),\n            })\n        end))\n        local grace = 50\n        local delta = timestamp() - start\n        assert.is_true(delta <= (100 + grace))\n        assert.is_true(delta >= (100 - grace))\n        assert.equals(1, one)\n        assert.equals(\"two\", two)\n        assert.equals(\"three\", three)\n        assert.equals(4, four)\n        assert.equals(5, five)\n    end)\n\n    it(\"should run all suspending functions concurrently\", function()\n        local start = timestamp()\n        local called = spy.new()\n        local function sleep(ms, ret_val)\n            return function()\n                a.sleep(ms)\n                called()\n                return ret_val\n            end\n        end\n        local first = a.run_blocking(a.wait_first, {\n            sleep(150, 1),\n            sleep(50, \"first\"),\n            sleep(150, \"three\"),\n            sleep(150, 4),\n            sleep(150, 5),\n        })\n        local grace = 20\n        local delta = timestamp() - start\n        assert.is_true(delta <= (50 + grace))\n        assert.equals(\"first\", first)\n    end)\n\n    it(\"should yield back immediately when not providing any functions\", function()\n        assert.is_nil(a.wait_first {})\n        assert.is_nil(a.wait_all {})\n    end)\nend)\n\ndescribe(\"async :: Condvar\", function()\n    local Condvar = control.Condvar\n\n    it(\"should block execution until condvar is notified\", function()\n        local condvar = Condvar:new()\n\n        local function wait()\n            local start = timestamp()\n            condvar:wait()\n            local stop = timestamp()\n            return stop - start\n        end\n\n        local start = timestamp()\n        local condvar_waits = a.run_blocking(function()\n            vim.defer_fn(function()\n                condvar:notify_all()\n            end, 110)\n            return _.table_pack(a.wait_all {\n                wait,\n                wait,\n                wait,\n                wait,\n            })\n        end)\n        local stop = timestamp()\n\n        for _, delay in ipairs(condvar_waits) do\n            assert.is_True(delay >= 100)\n        end\n        assert.is_true((stop - start) >= 100)\n    end)\nend)\n\ndescribe(\"async :: Semaphore\", function()\n    local Semaphore = control.Semaphore\n\n    it(\"should limit the amount of permits\", function()\n        local sem = Semaphore:new(5)\n        ---@type Permit[]\n        local permits = {}\n\n        local cancel_thread = a.run(function()\n            while true do\n                table.insert(permits, sem:acquire())\n            end\n        end)\n        cancel_thread()\n\n        assert.equals(5, #permits)\n    end)\n\n    it(\"should lease new permits\", function()\n        local sem = Semaphore:new(2)\n        ---@type Permit[]\n        local permits = {}\n\n        local cancel_thread = a.run(function()\n            while true do\n                table.insert(permits, sem:acquire())\n            end\n        end)\n\n        assert.equals(2, #permits)\n        permits[1]:forget()\n        permits[2]:forget()\n        assert.equals(4, #permits)\n        cancel_thread()\n    end)\nend)\n\ndescribe(\"async :: OneShotChannel\", function()\n    local OneShotChannel = control.OneShotChannel\n\n    it(\"should only allow sending once\", function()\n        local channel = OneShotChannel:new()\n        assert.is_false(channel:is_closed())\n        channel:send \"value\"\n        assert.is_true(channel:is_closed())\n        local err = assert.has_error(function()\n            channel:send \"value\"\n        end)\n        assert.equals(\"Oneshot channel can only send once.\", err)\n    end)\n\n    it(\"should wait until it can receive\", function()\n        local channel = OneShotChannel:new()\n\n        local start = timestamp()\n        local value = a.run_blocking(function()\n            vim.defer_fn(function()\n                channel:send(42)\n            end, 110)\n            return channel:receive()\n        end)\n        local stop = timestamp()\n\n        assert.is_true((stop - start) >= 100)\n        assert.equals(42, value)\n    end)\n\n    it(\"should receive immediately if value is already sent\", function()\n        local channel = OneShotChannel:new()\n        channel:send(42)\n        assert.equals(42, channel:receive())\n    end)\nend)\n\ndescribe(\"async :: Channel\", function()\n    local Channel = control.Channel\n\n    it(\"should suspend send until buffer is received\", function()\n        local channel = Channel:new()\n        spy.on(channel, \"send\")\n        local guard = spy.new()\n\n        a.run(function()\n            channel:send \"message\"\n            guard()\n            channel:send \"another message\"\n        end, function() end)\n\n        assert.spy(channel.send).was_called(1)\n        assert.spy(channel.send).was_called_with(match.is_ref(channel), \"message\")\n        assert.spy(guard).was_not_called()\n    end)\n\n    it(\"should send subsequent messages after they're received\", function()\n        local channel = Channel:new()\n        spy.on(channel, \"send\")\n\n        a.run(function()\n            channel:send \"message\"\n            channel:send \"another message\"\n        end, function() end)\n\n        local value = channel:receive()\n        assert.equals(value, \"message\")\n\n        assert.spy(channel.send).was_called(2)\n        assert.spy(channel.send).was_called_with(match.is_ref(channel), \"message\")\n        assert.spy(channel.send).was_called_with(match.is_ref(channel), \"another message\")\n    end)\n\n    it(\"should suspend receive until message is sent\", function()\n        local channel = Channel:new()\n\n        a.run(function()\n            a.sleep(100)\n            channel:send \"hello world\"\n        end, function() end)\n\n        local start = timestamp()\n        local value = a.run_blocking(function()\n            return channel:receive()\n        end)\n        local stop = timestamp()\n\n        assert.is_true((stop - start) > 80)\n        assert.equals(value, \"hello world\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/fetch_spec.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal fetch = require \"mason-core.fetch\"\nlocal match = require \"luassert.match\"\nlocal spawn = require \"mason-core.spawn\"\nlocal stub = require \"luassert.stub\"\nlocal version = require \"mason.version\"\n\ndescribe(\"fetch\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should exhaust all candidates\", function()\n        stub(spawn, \"wget\")\n        stub(spawn, \"curl\")\n        spawn.wget.returns(Result.failure \"wget failure\")\n        spawn.curl.returns(Result.failure \"curl failure\")\n\n        local result = fetch(\"https://api.github.com\", {\n            headers = { [\"X-Custom-Header\"] = \"here\" },\n        })\n        assert.is_true(result:is_failure())\n        assert.spy(spawn.wget).was_called(1)\n        assert.spy(spawn.curl).was_called(1)\n        assert.spy(spawn.wget).was_called_with {\n            {\n                {\n                    \"--header\",\n                    (\"User-Agent: mason.nvim %s (+https://github.com/mason-org/mason.nvim)\"):format(version.VERSION),\n                },\n                {\n                    \"--header\",\n                    \"X-Custom-Header: here\",\n                },\n            },\n            \"-o\",\n            \"/dev/null\",\n            \"-O\",\n            \"-\",\n            \"-T\",\n            30,\n            vim.NIL, -- body-data\n            \"https://api.github.com\",\n        }\n\n        assert.spy(spawn.curl).was_called_with(match.tbl_containing {\n            match.same {\n                {\n                    \"-H\",\n                    (\"User-Agent: mason.nvim %s (+https://github.com/mason-org/mason.nvim)\"):format(version.VERSION),\n                },\n                {\n                    \"-H\",\n                    \"X-Custom-Header: here\",\n                },\n            },\n            \"-fsSL\",\n            match.same { \"-X\", \"GET\" },\n            vim.NIL, -- data\n            vim.NIL, -- out file\n            match.same { \"--connect-timeout\", 30 },\n            \"https://api.github.com\",\n            on_spawn = match.is_function(),\n        })\n    end)\n\n    it(\"should return stdout\", function()\n        stub(spawn, \"curl\")\n        spawn.curl.returns(Result.success {\n            stdout = [[{\"data\": \"here\"}]],\n        })\n        local result = fetch \"https://api.github.com/data\"\n        assert.is_true(result:is_success())\n        assert.equals([[{\"data\": \"here\"}]], result:get_or_throw())\n    end)\n\n    it(\"should respect out_file opt\", function()\n        stub(spawn, \"wget\")\n        stub(spawn, \"curl\")\n        spawn.wget.returns(Result.failure \"wget failure\")\n        spawn.curl.returns(Result.failure \"curl failure\")\n        fetch(\"https://api.github.com/data\", { out_file = \"/test.json\" })\n\n        assert.spy(spawn.wget).was_called_with {\n            {\n                {\n                    \"--header\",\n                    (\"User-Agent: mason.nvim %s (+https://github.com/mason-org/mason.nvim)\"):format(version.VERSION),\n                },\n            },\n            \"-o\",\n            \"/dev/null\",\n            \"-O\",\n            \"/test.json\",\n            \"-T\",\n            30,\n            vim.NIL, -- body-data\n            \"https://api.github.com/data\",\n        }\n\n        assert.spy(spawn.curl).was_called_with(match.tbl_containing {\n            match.same {\n                {\n                    \"-H\",\n                    (\"User-Agent: mason.nvim %s (+https://github.com/mason-org/mason.nvim)\"):format(version.VERSION),\n                },\n            },\n            \"-fsSL\",\n            match.same { \"-X\", \"GET\" },\n            vim.NIL, -- data\n            match.same { \"-o\", \"/test.json\" },\n            match.same { \"--connect-timeout\", 30 },\n            \"https://api.github.com/data\",\n            on_spawn = match.is_function(),\n        })\n    end)\nend)\n\ndescribe(\"fetch :: wget\", function()\n    it(\"should reject non-supported HTTP methods\", function()\n        stub(spawn, \"wget\")\n        stub(spawn, \"curl\")\n        spawn.wget.returns(Result.failure \"wget failure\")\n        spawn.curl.returns(Result.failure \"curl failure\")\n        local PATCH_ERR = assert.has_error(function()\n            fetch(\"https://api.github.com/data\", { method = \"PATCH\" }):get_or_throw()\n        end)\n        local DELETE_ERR = assert.has_error(function()\n            fetch(\"https://api.github.com/data\", { method = \"DELETE\" }):get_or_throw()\n        end)\n        local PUT_ERR = assert.has_error(function()\n            fetch(\"https://api.github.com/data\", { method = \"PUT\" }):get_or_throw()\n        end)\n\n        assert.equals(\"fetch: wget doesn't support HTTP method PATCH\", PATCH_ERR)\n        assert.equals(\"fetch: wget doesn't support HTTP method DELETE\", DELETE_ERR)\n        assert.equals(\"fetch: wget doesn't support HTTP method PUT\", PUT_ERR)\n    end)\n\n    it(\"should reject requests with opts.data if not opts.method is not POST\", function()\n        stub(spawn, \"wget\")\n        stub(spawn, \"curl\")\n        spawn.wget.returns(Result.failure \"wget failure\")\n        spawn.curl.returns(Result.failure \"curl failure\")\n        local err = assert.has_error(function()\n            fetch(\"https://api.github.com/data\", { data = \"data\" }):get_or_throw()\n        end)\n\n        assert.equals(\"fetch: data provided but method is not POST (was GET)\", err)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/fs_spec.lua",
    "content": "local fs = require \"mason-core.fs\"\nlocal mason = require \"mason\"\n\ndescribe(\"fs\", function()\n    before_each(function()\n        mason.setup {\n            install_root_dir = \"/foo\",\n        }\n    end)\n\n    it(\"refuses to rmrf paths outside of boundary\", function()\n        local e = assert.has_error(function()\n            fs.sync.rmrf \"/thisisa/path\"\n        end)\n\n        assert.equals(\n            [[Refusing to rmrf \"/thisisa/path\" which is outside of the allowed boundary \"/foo\". Please report this error at https://github.com/mason-org/mason.nvim/issues/new]],\n            e\n        )\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/functional/data_spec.lua",
    "content": "local _ = require \"mason-core.functional\"\n\ndescribe(\"functional: data\", function()\n    it(\"creates enums\", function()\n        local colors = _.enum {\n            \"BLUE\",\n            \"YELLOW\",\n        }\n        assert.same({\n            [\"BLUE\"] = \"BLUE\",\n            [\"YELLOW\"] = \"YELLOW\",\n        }, colors)\n    end)\n\n    it(\"creates sets\", function()\n        local colors = _.set_of {\n            \"BLUE\",\n            \"YELLOW\",\n            \"BLUE\",\n            \"RED\",\n        }\n        assert.same({\n            [\"BLUE\"] = true,\n            [\"YELLOW\"] = true,\n            [\"RED\"] = true,\n        }, colors)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/functional/function_spec.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal match = require \"luassert.match\"\nlocal spy = require \"luassert.spy\"\n\ndescribe(\"functional: function\", function()\n    it(\"curries functions\", function()\n        local function sum(...)\n            local res = 0\n            for i = 1, select(\"#\", ...) do\n                res = res + select(i, ...)\n            end\n            return res\n        end\n        local arity0 = _.curryN(sum, 0)\n        local arity1 = _.curryN(sum, 1)\n        local arity2 = _.curryN(sum, 2)\n        local arity3 = _.curryN(sum, 3)\n\n        assert.equals(0, arity0(42))\n        assert.equals(42, arity1(42))\n        assert.equals(3, arity2(1)(2))\n        assert.equals(3, arity2(1, 2))\n        assert.equals(6, arity3(1)(2)(3))\n        assert.equals(6, arity3(1, 2, 3))\n\n        -- should discard superfluous args\n        assert.equals(0, arity1(0, 10, 20, 30))\n    end)\n\n    it(\"coalesces first non-nil value\", function()\n        assert.equals(\"Hello World!\", _.coalesce(nil, nil, \"Hello World!\", \"\"))\n    end)\n\n    it(\"should compose functions\", function()\n        local function add(x)\n            return function(y)\n                return y + x\n            end\n        end\n        local function subtract(x)\n            return function(y)\n                return y - x\n            end\n        end\n        local function multiply(x)\n            return function(y)\n                return y * x\n            end\n        end\n\n        local big_maths = _.compose(add(1), subtract(3), multiply(5))\n\n        assert.equals(23, big_maths(5))\n    end)\n\n    it(\"should not allow composing no functions\", function()\n        local e = assert.has_error(function()\n            _.compose()\n        end)\n        assert.equals(\"compose requires at least one function\", e)\n    end)\n\n    it(\"should partially apply functions\", function()\n        local funcy = spy.new()\n        local partially_funcy = _.partial(funcy, \"a\", \"b\", \"c\")\n        partially_funcy(\"d\", \"e\", \"f\")\n        assert.spy(funcy).was_called_with(\"a\", \"b\", \"c\", \"d\", \"e\", \"f\")\n    end)\n\n    it(\"should partially apply functions with nil arguments\", function()\n        local funcy = spy.new()\n        local partially_funcy = _.partial(funcy, \"a\", nil, \"c\")\n        partially_funcy(\"d\", nil, \"f\")\n        assert.spy(funcy).was_called_with(\"a\", nil, \"c\", \"d\", nil, \"f\")\n    end)\n\n    it(\"memoizes functions with default cache mechanism\", function()\n        local expensive_function = spy.new(function(s)\n            return s\n        end)\n        local memoized_fn = _.memoize(expensive_function)\n        assert.equals(\"key\", memoized_fn \"key\")\n        assert.equals(\"key\", memoized_fn \"key\")\n        assert.equals(\"new_key\", memoized_fn \"new_key\")\n        assert.spy(expensive_function).was_called(2)\n    end)\n\n    it(\"memoizes function with custom cache mechanism\", function()\n        local expensive_function = spy.new(function(arg1, arg2)\n            return arg1 .. arg2\n        end)\n        local memoized_fn = _.memoize(expensive_function, function(arg1, arg2)\n            return arg1 .. arg2\n        end)\n        assert.equals(\"key1key2\", memoized_fn(\"key1\", \"key2\"))\n        assert.equals(\"key1key2\", memoized_fn(\"key1\", \"key2\"))\n        assert.equals(\"key1key3\", memoized_fn(\"key1\", \"key3\"))\n        assert.spy(expensive_function).was_called(2)\n    end)\n\n    it(\"should evaluate functions lazily\", function()\n        local impl = spy.new(function()\n            return {}, {}\n        end)\n        local lazy_fn = _.lazy(impl)\n        assert.spy(impl).was_called(0)\n        local a, b = lazy_fn()\n        assert.spy(impl).was_called(1)\n        assert.is_true(match.is_table()(a))\n        assert.is_true(match.is_table()(b))\n        local new_a, new_b = lazy_fn()\n        assert.spy(impl).was_called(1)\n        assert.is_true(match.is_ref(a)(new_a))\n        assert.is_true(match.is_ref(b)(new_b))\n    end)\n\n    it(\"should support nil return values in lazy functions\", function()\n        local lazy_fn = _.lazy(function()\n            return nil, 2\n        end)\n        local a, b = lazy_fn()\n        assert.is_nil(a)\n        assert.equals(2, b)\n    end)\n\n    it(\"should provide identity value\", function()\n        local obj = {}\n        assert.equals(2, _.identity(2))\n        assert.equals(obj, _.identity(obj))\n    end)\n\n    it(\"should always return bound value\", function()\n        local obj = {}\n        assert.equals(2, _.always(2)())\n        assert.equals(obj, _.always(obj)())\n    end)\n\n    it(\"true is true and false is false\", function()\n        assert.is_true(_.T())\n        assert.is_false(_.F())\n    end)\n\n    it(\"should tap values\", function()\n        local fn = spy.new()\n        assert.equals(42, _.tap(fn, 42))\n        assert.spy(fn).was_called()\n        assert.spy(fn).was_called_with(42)\n    end)\n\n    it(\"should apply function\", function()\n        local max = spy.new(math.max)\n        local max_fn = _.apply(max)\n        assert.equals(42, max_fn { 1, 2, 3, 4, 5, 6, 7, 8, 9, 42, 10, 8, 4 })\n        assert.spy(max).was_called(1)\n        assert.spy(max).was_called_with(1, 2, 3, 4, 5, 6, 7, 8, 9, 42, 10, 8, 4)\n    end)\n\n    it(\"should apply value to function\", function()\n        local agent = spy.new()\n        _.apply_to(\"007\", agent)\n        assert.spy(agent).was_called(1)\n        assert.spy(agent).was_called_with \"007\"\n    end)\n\n    it(\"should converge on function\", function()\n        local target = spy.new()\n        _.converge(target, { _.head, _.last }, { \"These\", \"Are\", \"Some\", \"Words\", \"Ain't\", \"That\", \"Pretty\", \"Nuts\" })\n        assert.spy(target).was_called(1)\n        assert.spy(target).was_called_with(\"These\", \"Nuts\")\n    end)\n\n    it(\"should apply spec\", function()\n        local apply = _.apply_spec {\n            sum = _.add(2),\n            list = { _.add(2), _.add(6) },\n            nested = {\n                sum = _.min(2),\n            },\n        }\n        assert.same({\n            sum = 4,\n            list = { 4, 8 },\n            nested = {\n                sum = 0,\n            },\n        }, apply(2))\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/functional/list_spec.lua",
    "content": "local Optional = require \"mason-core.optional\"\nlocal _ = require \"mason-core.functional\"\nlocal spy = require \"luassert.spy\"\n\ndescribe(\"functional: list\", function()\n    it(\"should produce list without nils\", function()\n        assert.same({ 1, 2, 3, 4 }, _.list_not_nil(nil, 1, 2, nil, 3, nil, 4, nil))\n    end)\n\n    it(\"makes a shallow copy of a list\", function()\n        local list = { \"BLUE\", { nested = \"TABLE\" }, \"RED\" }\n        local list_copy = _.list_copy(list)\n        assert.same({ \"BLUE\", { nested = \"TABLE\" }, \"RED\" }, list_copy)\n        assert.is_false(list == list_copy)\n        assert.is_true(list[2] == list_copy[2])\n    end)\n\n    it(\"reverses lists\", function()\n        local colors = { \"BLUE\", \"YELLOW\", \"RED\" }\n        assert.same({\n            \"RED\",\n            \"YELLOW\",\n            \"BLUE\",\n        }, _.reverse(colors))\n        -- should not modify in-place\n        assert.same({ \"BLUE\", \"YELLOW\", \"RED\" }, colors)\n    end)\n\n    it(\"maps over list\", function()\n        local colors = { \"BLUE\", \"YELLOW\", \"RED\" }\n        assert.same(\n            {\n                \"LIGHT_BLUE\",\n                \"LIGHT_YELLOW\",\n                \"LIGHT_RED\",\n            },\n            _.map(function(color)\n                return \"LIGHT_\" .. color\n            end, colors)\n        )\n        -- should not modify in-place\n        assert.same({ \"BLUE\", \"YELLOW\", \"RED\" }, colors)\n    end)\n\n    it(\"filter_map over list\", function()\n        local colors = { \"BROWN\", \"BLUE\", \"YELLOW\", \"GREEN\", \"CYAN\" }\n        assert.same(\n            {\n                \"BROWN EYES\",\n                \"BLUE EYES\",\n                \"GREEN EYES\",\n            },\n            _.filter_map(function(color)\n                if _.any_pass({ _.equals \"BROWN\", _.equals \"BLUE\", _.equals \"GREEN\" }, color) then\n                    return Optional.of((\"%s EYES\"):format(color))\n                else\n                    return Optional.empty()\n                end\n            end, colors)\n        )\n    end)\n\n    it(\"finds first item that fulfills predicate\", function()\n        local predicate = spy.new(function(item)\n            return item == \"Waldo\"\n        end)\n\n        assert.equals(\n            \"Waldo\",\n            _.find_first(predicate, {\n                \"Where\",\n                \"On Earth\",\n                \"Is\",\n                \"Waldo\",\n                \"?\",\n            })\n        )\n        assert.spy(predicate).was.called(4)\n    end)\n\n    it(\"determines whether any item in the list fulfills predicate\", function()\n        local predicate = spy.new(function(item)\n            return item == \"On Earth\"\n        end)\n\n        assert.is_true(_.any(predicate, {\n            \"Where\",\n            \"On Earth\",\n            \"Is\",\n            \"Waldo\",\n            \"?\",\n        }))\n\n        assert.spy(predicate).was.called(2)\n    end)\n\n    it(\"should check that all items in list fulfills predicate\", function()\n        assert.is_true(_.all(_.is \"string\", {\n            \"Where\",\n            \"On Earth\",\n            \"Is\",\n            \"Waldo\",\n            \"?\",\n        }))\n\n        local predicate = spy.new(_.is \"string\")\n\n        assert.is_false(_.all(predicate, {\n            \"Five\",\n            \"Plus\",\n            42,\n            \"Equals\",\n            47,\n        }))\n        assert.spy(predicate).was_called(3)\n    end)\n\n    it(\"should iterate list in .each\", function()\n        local list = { \"BLUE\", \"YELLOW\", \"RED\" }\n        local iterate_fn = spy.new()\n        _.each(iterate_fn, list)\n        assert.spy(iterate_fn).was_called(3)\n        assert.spy(iterate_fn).was_called_with(\"BLUE\", 1)\n        assert.spy(iterate_fn).was_called_with(\"YELLOW\", 2)\n        assert.spy(iterate_fn).was_called_with(\"RED\", 3)\n    end)\n\n    it(\"should concat list tables\", function()\n        local list = { \"monstera\", \"tulipa\", \"carnation\" }\n        assert.same({ \"monstera\", \"tulipa\", \"carnation\", \"rose\", \"daisy\" }, _.concat(list, { \"rose\", \"daisy\" }))\n        assert.same({ \"monstera\", \"tulipa\", \"carnation\" }, list) -- does not mutate list\n    end)\n\n    it(\"should concat strings\", function()\n        assert.equals(\"FooBar\", _.concat(\"Foo\", \"Bar\"))\n    end)\n\n    it(\"should zip list into table\", function()\n        local fnkey = function() end\n        assert.same({\n            a = \"a\",\n            [fnkey] = 1,\n        }, _.zip_table({ \"a\", fnkey }, { \"a\", 1 }))\n    end)\n\n    it(\"should get nth item\", function()\n        assert.equals(\"first\", _.nth(1, { \"first\", \"middle\", \"last\" }))\n        assert.equals(\"last\", _.nth(-1, { \"first\", \"middle\", \"last\" }))\n        assert.equals(\"middle\", _.nth(-2, { \"first\", \"middle\", \"last\" }))\n        assert.equals(\"a\", _.nth(1, \"abc\"))\n        assert.equals(\"c\", _.nth(-1, \"abc\"))\n        assert.equals(\"b\", _.nth(-2, \"abc\"))\n        assert.is_nil(_.nth(0, { \"value\" }))\n        assert.equals(\"\", _.nth(0, \"abc\"))\n    end)\n\n    it(\"should get length\", function()\n        assert.equals(0, _.length {})\n        assert.equals(0, _.length { nil })\n        assert.equals(0, _.length { obj = \"doesnt count\" })\n        assert.equals(0, _.length \"\")\n        assert.equals(1, _.length { \"\" })\n        assert.equals(4, _.length \"fire\")\n    end)\n\n    it(\"should sort by comparator\", function()\n        local list = {\n            {\n                name = \"William\",\n            },\n            {\n                name = \"Boman\",\n            },\n        }\n        assert.same({\n            {\n                name = \"Boman\",\n            },\n            {\n                name = \"William\",\n            },\n        }, _.sort_by(_.prop \"name\", list))\n\n        -- Should not mutate original list\n        assert.same({\n            {\n                name = \"William\",\n            },\n            {\n                name = \"Boman\",\n            },\n        }, list)\n    end)\n\n    it(\"should append to list\", function()\n        local list = { \"Earth\", \"Wind\" }\n        assert.same({ \"Earth\", \"Wind\", { \"Fire\" } }, _.append({ \"Fire\" }, list))\n\n        -- Does not mutate original list\n        assert.same({ \"Earth\", \"Wind\" }, list)\n    end)\n\n    it(\"should prepend to list\", function()\n        local list = { \"Fire\" }\n        assert.same({ { \"Earth\", \"Wind\" }, \"Fire\" }, _.prepend({ \"Earth\", \"Wind\" }, list))\n\n        -- Does not mutate original list\n        assert.same({ \"Fire\" }, list)\n    end)\n\n    it(\"joins lists\", function()\n        assert.equals(\"Hello, John\", _.join(\", \", { \"Hello\", \"John\" }))\n    end)\n\n    it(\"should uniq_by lists\", function()\n        local list = { \"Person.\", \"Woman.\", \"Man.\", \"Person.\", \"Woman.\", \"Camera.\", \"TV.\" }\n        assert.same({ \"Person.\", \"Woman.\", \"Man.\", \"Camera.\", \"TV.\" }, _.uniq_by(_.identity, list))\n    end)\n\n    it(\"should partition lists\", function()\n        local words = { \"person\", \"Woman\", \"Man\", \"camera\", \"TV\" }\n        assert.same({\n            { \"Woman\", \"Man\", \"TV\" },\n            { \"person\", \"camera\" },\n        }, _.partition(_.matches \"%u\", words))\n    end)\n\n    it(\"should return head\", function()\n        assert.equals(\"Head\", _.head { \"Head\", \"Tail\", \"Tail\" })\n    end)\n\n    it(\"should return last\", function()\n        assert.equals(\"Last\", _.last { \"Head\", \"List\", \"Last\" })\n    end)\n\n    it(\"should take n items\", function()\n        local list = { \"First\", \"Second\", \"Third\", \"I\", \"Have\", \"Poor\", \"Imagination\" }\n        assert.same({ \"First\", \"Second\", \"Third\" }, _.take(3, list))\n        assert.same({}, _.take(0, list))\n        assert.same({ \"First\", \"Second\", \"Third\", \"I\", \"Have\", \"Poor\", \"Imagination\" }, _.take(10000, list))\n    end)\n\n    it(\"should drop n items\", function()\n        local list = { \"First\", \"Second\", \"Third\", \"I\", \"Have\", \"Poor\", \"Imagination\" }\n        assert.same({ \"I\", \"Have\", \"Poor\", \"Imagination\" }, _.drop(3, list))\n        assert.same({ \"First\", \"Second\", \"Third\", \"I\", \"Have\", \"Poor\", \"Imagination\" }, _.drop(0, list))\n        assert.same({}, _.drop(10000, list))\n    end)\n\n    it(\"should drop last n items\", function()\n        local list = { \"First\", \"Second\", \"Third\", \"I\", \"Have\", \"Poor\", \"Imagination\" }\n        assert.same({ \"First\", \"Second\", \"Third\" }, _.drop_last(4, list))\n        assert.same({ \"First\", \"Second\", \"Third\", \"I\", \"Have\", \"Poor\", \"Imagination\" }, _.drop_last(0, list))\n        assert.same({}, _.drop_last(10000, list))\n    end)\n\n    it(\"should reduce lists\", function()\n        local add = spy.new(_.add)\n        assert.equals(15, _.reduce(add, 0, { 1, 2, 3, 4, 5 }))\n        assert.spy(add).was_called(5)\n        assert.spy(add).was_called_with(0, 1)\n        assert.spy(add).was_called_with(1, 2)\n        assert.spy(add).was_called_with(3, 3)\n        assert.spy(add).was_called_with(6, 4)\n        assert.spy(add).was_called_with(10, 5)\n    end)\n\n    it(\"should split lists\", function()\n        assert.same({\n            { 1, 2, 3 },\n            { 4, 5, 6 },\n            { 7 },\n        }, _.split_every(3, { 1, 2, 3, 4, 5, 6, 7 }))\n\n        assert.same({ { 1, 2, 3 } }, _.split_every(5, { 1, 2, 3 }))\n        assert.same({ { 1 }, { 2 }, { 3 } }, _.split_every(1, { 1, 2, 3 }))\n\n        assert.has_error(function()\n            _.split_every(0, {})\n        end)\n    end)\n\n    it(\"should index_by lists\", function()\n        assert.same(\n            {\n                apple = { fruit = \"apple\", color = \"red\" },\n                banana = { fruit = \"banana\", color = \"yellow\" },\n            },\n            _.index_by(_.prop \"fruit\", {\n                { fruit = \"apple\", color = \"red\" },\n                { fruit = \"banana\", color = \"yellow\" },\n            })\n        )\n    end)\n\n    it(\"should flatten tables\", function()\n        assert.same({ 1, 2, 3 }, _.flatten { 1, 2, 3 })\n        assert.same({ 1, 2, 3, \"a\" }, _.flatten { 1, { 2 }, { 3 }, \"a\" })\n        assert.same({ 1, 2, 3, 4, 5 }, _.flatten { 1, { { 2, 3 }, { 4 } }, { 5 } })\n    end)\n\n    -- Note: this is not necessarily a requirement, but it is expected to behave this way as of writing.\n    it(\"should flatten keyed tables\", function()\n        assert.same(\n            {\n                \"-xvf\",\n                \"file\",\n            },\n            _.flatten {\n                { \"-xvf\", { \"file\" } },\n                cmd = \"tar\",\n                env = {\n                    LC_ALL = \"latin\",\n                },\n            }\n        )\n    end)\nend)\n\ndescribe(\"list immutability\", function()\n    it(\"should not mutate lists\", function()\n        local og_list = setmetatable({ \"a\", \"b\", \"c\" }, {\n            __newindex = function()\n                error \"Tried to newindex\"\n            end,\n        })\n\n        _.reverse(og_list)\n        _.list_copy(og_list)\n        _.filter(_.F, og_list)\n        _.map(_.to_upper, og_list)\n        _.filter_map(_.always(Optional.empty()), og_list)\n        _.each(_.length, og_list)\n        _.concat(og_list, { \"d\", \"e\" })\n        _.append(\"d\", og_list)\n        _.prepend(\"0\", og_list)\n        _.zip_table({ \"first\", \"second\", \"third\" }, og_list)\n        _.nth(1, og_list)\n        _.head(og_list)\n        _.last(og_list)\n        _.length(og_list)\n        _.flatten(og_list)\n        _.sort_by(_.identity, og_list)\n        _.uniq_by(_.identity, og_list)\n        _.join(\".\", og_list)\n        _.partition(_.equals \"a\", og_list)\n        _.take(2, og_list)\n        _.drop(2, og_list)\n        _.drop_last(2, og_list)\n        _.reduce(_.concat, \"\", og_list)\n\n        assert.same({ \"a\", \"b\", \"c\" }, og_list)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/functional/logic_spec.lua",
    "content": "local _ = require \"mason-core.functional\"\nlocal spy = require \"luassert.spy\"\n\ndescribe(\"functional: logic\", function()\n    it(\"should check that all_pass checks that all predicates pass\", function()\n        local is_waldo = _.equals \"waldo\"\n        assert.is_true(_.all_pass { _.T, _.T, is_waldo, _.T } \"waldo\")\n        assert.is_false(_.all_pass { _.T, _.T, is_waldo, _.F } \"waldo\")\n        assert.is_false(_.all_pass { _.T, _.T, is_waldo, _.T } \"waldina\")\n    end)\n\n    it(\"should check that any_pass checks that any predicates pass\", function()\n        local is_waldo = _.equals \"waldo\"\n        local is_waldina = _.equals \"waldina\"\n        local is_luigi = _.equals \"luigi\"\n\n        assert.is_true(_.any_pass { is_waldo, is_waldina } \"waldo\")\n        assert.is_false(_.any_pass { is_waldina, is_luigi } \"waldo\")\n        assert.is_true(_.any_pass { is_waldina, is_luigi } \"waldina\")\n    end)\n\n    it(\"should branch if_else\", function()\n        local a = spy.new()\n        local b = spy.new()\n        _.if_else(_.T, a, b) \"a\"\n        _.if_else(_.F, a, b) \"b\"\n        assert.spy(a).was_called(1)\n        assert.spy(a).was_called_with \"a\"\n        assert.spy(b).was_called(1)\n        assert.spy(b).was_called_with \"b\"\n    end)\n\n    it(\"should flip booleans\", function()\n        assert.is_true(_.is_not(false))\n        assert.is_false(_.is_not(true))\n    end)\n\n    it(\"should resolve correct cond\", function()\n        local planetary_object = _.cond {\n            {\n                _.equals \"Moon!\",\n                _.format \"to the %s\",\n            },\n            {\n                _.equals \"World!\",\n                _.format \"Hello %s\",\n            },\n        }\n        assert.equals(\"Hello World!\", planetary_object \"World!\")\n        assert.equals(\"to the Moon!\", planetary_object \"Moon!\")\n    end)\n\n    it(\"should give complements\", function()\n        assert.is_true(_.complement(_.is_nil, \"not nil\"))\n        assert.is_false(_.complement(_.is_nil, nil))\n    end)\n\n    it(\"should default to provided value\", function()\n        local fortytwo = _.default_to(42)\n        assert.equals(42, fortytwo(nil))\n        assert.equals(1337, fortytwo(1337))\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/functional/number_spec.lua",
    "content": "local _ = require \"mason-core.functional\"\n\ndescribe(\"functional: number\", function()\n    it(\"should negate numbers\", function()\n        assert.equals(-42, _.negate(42))\n        assert.equals(42, _.negate(-42))\n    end)\n\n    it(\"should check numbers greater than value\", function()\n        local greater_than_life = _.gt(42)\n        assert.is_false(greater_than_life(0))\n        assert.is_false(greater_than_life(42))\n        assert.is_true(greater_than_life(43))\n    end)\n\n    it(\"should check numbers greater or equal than value\", function()\n        local greater_or_equal_to_life = _.gte(42)\n        assert.is_false(greater_or_equal_to_life(0))\n        assert.is_true(greater_or_equal_to_life(42))\n        assert.is_true(greater_or_equal_to_life(43))\n    end)\n\n    it(\"should check numbers lower than value\", function()\n        local lesser_than_life = _.lt(42)\n        assert.is_true(lesser_than_life(0))\n        assert.is_false(lesser_than_life(42))\n        assert.is_false(lesser_than_life(43))\n    end)\n\n    it(\"should check numbers lower or equal than value\", function()\n        local lesser_or_equal_to_life = _.lte(42)\n        assert.is_true(lesser_or_equal_to_life(0))\n        assert.is_true(lesser_or_equal_to_life(42))\n        assert.is_false(lesser_or_equal_to_life(43))\n    end)\n\n    it(\"should increment numbers\", function()\n        local add_5 = _.inc(5)\n        assert.equals(0, add_5(-5))\n        assert.equals(5, add_5(0))\n        assert.equals(7, add_5(2))\n    end)\n\n    it(\"should decrement numbers\", function()\n        local subtract_5 = _.dec(5)\n        assert.equals(5, subtract_5(10))\n        assert.equals(-5, subtract_5(0))\n        assert.equals(-3, subtract_5(2))\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/functional/relation_spec.lua",
    "content": "local _ = require \"mason-core.functional\"\n\ndescribe(\"functional: relation\", function()\n    it(\"should check equality\", function()\n        local tbl = {}\n        local is_tbl = _.equals(tbl)\n        local is_a = _.equals \"a\"\n        local is_42 = _.equals(42)\n\n        assert.is_true(is_tbl(tbl))\n        assert.is_true(is_a \"a\")\n        assert.is_true(is_42(42))\n        assert.is_false(is_a \"b\")\n        assert.is_false(is_42(32))\n    end)\n\n    it(\"should check non-equality\", function()\n        local tbl = {}\n        local is_not_tbl = _.not_equals(tbl)\n        local is_not_a = _.not_equals \"a\"\n        local is_not_42 = _.not_equals(42)\n\n        assert.is_false(is_not_tbl(tbl))\n        assert.is_false(is_not_a \"a\")\n        assert.is_false(is_not_42(42))\n        assert.is_true(is_not_a \"b\")\n        assert.is_true(is_not_42(32))\n    end)\n\n    it(\"should check property equality\", function()\n        local fn_key = function() end\n        local tbl = { a = \"a\", b = \"b\", number = 42, [fn_key] = \"fun\" }\n        assert.is_true(_.prop_eq(\"a\", \"a\", tbl))\n        assert.is_true(_.prop_eq(fn_key, \"fun\", tbl))\n        assert.is_true(_.prop_eq(fn_key) \"fun\"(tbl))\n    end)\n\n    it(\"should check whether property satisfies predicate\", function()\n        local obj = {\n            low = 0,\n            med = 10,\n            high = 15,\n        }\n\n        assert.is_false(_.prop_satisfies(_.gt(10), \"low\", obj))\n        assert.is_false(_.prop_satisfies(_.gt(10), \"med\")(obj))\n        assert.is_true(_.prop_satisfies(_.gt(10)) \"high\"(obj))\n    end)\n\n    it(\"should check whether nested property satisfies predicate\", function()\n        local obj = {\n            low = { value = 0 },\n            med = { value = 10 },\n            high = { value = 15 },\n        }\n\n        assert.is_false(_.path_satisfies(_.gt(10), { \"low\", \"value\" }, obj))\n        assert.is_false(_.path_satisfies(_.gt(10), { \"med\", \"value\" })(obj))\n        assert.is_true(_.path_satisfies(_.gt(10)) { \"high\", \"value\" }(obj))\n    end)\n\n    it(\"should subtract numbers\", function()\n        assert.equals(42, _.min(42, 84))\n        assert.equals(-1, _.min(11, 10))\n    end)\n\n    it(\"should add numbers\", function()\n        assert.equals(1337, _.add(1300, 37))\n        assert.equals(-10, _.add(90, -100))\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/functional/string_spec.lua",
    "content": "local _ = require \"mason-core.functional\"\n\ndescribe(\"functional: string\", function()\n    it(\"matches string patterns\", function()\n        assert.is_true(_.matches(\"foo\", \"foo\"))\n        assert.is_true(_.matches(\"bar\", \"foobarbaz\"))\n        assert.is_true(_.matches(\"ba+r\", \"foobaaaaaaarbaz\"))\n\n        assert.is_false(_.matches(\"ba+r\", \"foobharbaz\"))\n        assert.is_false(_.matches(\"bar\", \"foobaz\"))\n    end)\n\n    it(\"returns string pattern matches\", function()\n        assert.same({ \"foo\" }, _.match(\"foo\", \"foo\"))\n        assert.same({ \"foo\", \"bar\", \"baz\" }, _.match(\"(foo) (bar) (baz)\", \"foo bar baz\"))\n    end)\n\n    it(\"should format strings\", function()\n        assert.equals(\"Hello World!\", _.format(\"%s\", \"Hello World!\"))\n        assert.equals(\"special manouvers\", _.format(\"%s manouvers\", \"special\"))\n    end)\n\n    it(\"should split strings\", function()\n        assert.same({ \"This\", \"is\", \"a\", \"sentence\" }, _.split(\"%s\", \"This is a sentence\"))\n        assert.same({ \"This\", \"is\", \"a\", \"sentence\" }, _.split(\"|\", \"This|is|a|sentence\"))\n    end)\n\n    it(\"should gsub strings\", function()\n        assert.same(\"predator\", _.gsub(\"^apex%s*\", \"\", \"apex predator\"))\n    end)\n\n    it(\"should dedent strings\", function()\n        assert.equals(\n            [[Lorem\nIpsum\n    Dolor\n  Sit\n Amet]],\n            _.dedent [[\n    Lorem\n    Ipsum\n        Dolor\n      Sit\n     Amet\n]]\n        )\n    end)\n\n    it(\"should transform casing\", function()\n        assert.equals(\"HELLO!\", _.to_upper \"Hello!\")\n        assert.equals(\"hello!\", _.to_lower \"Hello!\")\n    end)\n\n    it(\"trim strings\", function()\n        assert.equals(\"HELLO!\", _.trim \"   HELLO!  \")\n    end)\n\n    it(\"should trim_start strings\", function()\n        assert.equals(\"HELLO!  \", _.trim_start_matches(\"%s\", \"\t   HELLO!  \"))\n    end)\n\n    it(\"should trim_end strings\", function()\n        assert.equals(\"\t   HELLO!\", _.trim_end_matches(\"%s\", \"\t   HELLO!  \"))\n    end)\n\n    it(\"should strip_prefix\", function()\n        assert.equals(\"withthewind\", _.strip_prefix(\"gone\", \"gonewiththewind\"))\n        assert.equals(\"1.3.0\", _.strip_prefix(\"v\", \"v1.3.0\"))\n        assert.equals(\"-30\", _.strip_prefix(\"2023-05\", \"2023-05-30\"))\n        assert.equals(\"The same\", _.strip_prefix(\"Not Equals\", \"The same\"))\n        assert.equals(\"long_pattern\", _.strip_prefix(\"long_pattern_here\", \"long_pattern\"))\n        assert.equals(\"\", _.strip_prefix(\"pattern_here\", \"pattern_here\"))\n        assert.equals(\"s\", _.strip_prefix(\"pattern_here\", \"pattern_heres\"))\n    end)\n\n    it(\"should strip_suffix\", function()\n        assert.equals(\"gone\", _.strip_suffix(\"withtthewind\", \"gonewithtthewind\"))\n        assert.equals(\"name\", _.strip_suffix(\".tar.gz\", \"name.tar.gz\"))\n        assert.equals(\"2023\", _.strip_suffix(\"-05-30\", \"2023-05-30\"))\n        assert.equals(\"The same\", _.strip_suffix(\"Not Equals\", \"The same\"))\n        assert.equals(\"pattern_here\", _.strip_suffix(\"long_pattern_here\", \"pattern_here\"))\n        assert.equals(\"\", _.strip_suffix(\"pattern_here\", \"pattern_here\"))\n        assert.equals(\"s\", _.strip_suffix(\"pattern_here\", \"spattern_here\"))\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/functional/table_spec.lua",
    "content": "local _ = require \"mason-core.functional\"\n\ndescribe(\"functional: table\", function()\n    it(\"retrieves property of table\", function()\n        assert.equals(\"hello\", _.prop(\"a\", { a = \"hello\" }))\n    end)\n\n    it(\"retrieves nested property of table\", function()\n        assert.equals(\"hello\", _.path({ \"a\", \"greeting\" }, { a = { greeting = \"hello\" } }))\n    end)\n\n    it(\"picks properties of table\", function()\n        local function fn() end\n        assert.same(\n            {\n                [\"key1\"] = 1,\n                [fn] = 2,\n            },\n            _.pick({ \"key1\", fn }, {\n                [\"key1\"] = 1,\n                [fn] = 2,\n                [3] = 3,\n            })\n        )\n    end)\n\n    it(\"converts table to pairs\", function()\n        assert.same(\n            _.sort_by(_.nth(1), {\n                {\n                    \"skies\",\n                    \"cloudy\",\n                },\n                {\n                    \"temperature\",\n                    \"20°\",\n                },\n            }),\n            _.sort_by(_.nth(1), _.to_pairs { skies = \"cloudy\", temperature = \"20°\" })\n        )\n    end)\n\n    it(\"converts pairs to table\", function()\n        assert.same(\n            { skies = \"cloudy\", temperature = \"20°\" },\n            _.from_pairs {\n                {\n                    \"skies\",\n                    \"cloudy\",\n                },\n                {\n                    \"temperature\",\n                    \"20°\",\n                },\n            }\n        )\n    end)\n\n    it(\"should invert tables\", function()\n        assert.same(\n            {\n                val1 = \"key1\",\n                val2 = \"key2\",\n            },\n            _.invert {\n                key1 = \"val1\",\n                key2 = \"val2\",\n            }\n        )\n    end)\n\n    it(\"should evolve table\", function()\n        assert.same(\n            {\n                non_existent = nil,\n                firstname = \"JOHN\",\n                lastname = \"DOE\",\n                age = 42,\n            },\n            _.evolve({\n                non_existent = _.always \"hello\",\n                firstname = _.to_upper,\n                lastname = _.to_upper,\n                age = _.add(2),\n            }, {\n                firstname = \"John\",\n                lastname = \"Doe\",\n                age = 40,\n            })\n        )\n    end)\n\n    it(\"should merge left\", function()\n        assert.same(\n            {\n                firstname = \"John\",\n                lastname = \"Doe\",\n            },\n            _.merge_left({\n                firstname = \"John\",\n            }, {\n                firstname = \"Jane\",\n                lastname = \"Doe\",\n            })\n        )\n    end)\n\n    it(\"should dissoc keys\", function()\n        assert.same({\n            a = \"a\",\n            c = \"c\",\n        }, _.dissoc(\"b\", { a = \"a\", b = \"b\", c = \"c\" }))\n    end)\n\n    it(\"should assoc keys\", function()\n        assert.same({\n            a = \"a\",\n            b = \"b\",\n            c = \"c\",\n        }, _.assoc(\"b\", \"b\", { a = \"a\", c = \"c\" }))\n    end)\nend)\n\ndescribe(\"table immutability\", function()\n    it(\"should not mutate tables\", function()\n        local og_table = setmetatable({ key = \"value\", imagination = \"poor\", hotel = \"trivago\" }, {\n            __newindex = function()\n                error \"Tried to newindex\"\n            end,\n        })\n\n        _.prop(\"hotel\", og_table)\n        _.path({ \"hotel\" }, og_table)\n        _.pick({ \"hotel\" }, og_table)\n        _.keys(og_table)\n        _.size(og_table)\n        _.from_pairs(_.to_pairs(og_table))\n        _.invert(og_table)\n        _.evolve({ hotel = _.to_upper }, og_table)\n        _.merge_left(og_table, {})\n        _.assoc(\"new\", \"value\", og_table)\n        _.dissoc(\"hotel\", og_table)\n\n        assert.same({ key = \"value\", imagination = \"poor\", hotel = \"trivago\" }, og_table)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/functional/type_spec.lua",
    "content": "local _ = require \"mason-core.functional\"\n\ndescribe(\"functional: type\", function()\n    it(\"should check nil value\", function()\n        assert.is_true(_.is_nil(nil))\n        assert.is_false(_.is_nil(1))\n        assert.is_false(_.is_nil {})\n        assert.is_false(_.is_nil(function() end))\n    end)\n\n    it(\"should check types\", function()\n        local is_fun = _.is \"function\"\n        local is_string = _.is \"string\"\n        local is_number = _.is \"number\"\n        local is_boolean = _.is \"boolean\"\n\n        assert.is_true(is_fun(function() end))\n        assert.is_false(is_fun(1))\n        assert.is_true(is_string \"\")\n        assert.is_false(is_string(1))\n        assert.is_true(is_number(1))\n        assert.is_false(is_number \"\")\n        assert.is_true(is_boolean(true))\n        assert.is_false(is_boolean(1))\n    end)\n\n    it(\"should check is_list\", function()\n        assert.is_true(_.is_list {})\n        assert.is_true(_.is_list { 1, 2, 3 })\n        assert.is_true(_.is_list { 1, \"a\" })\n        assert.is_false(_.is_list(vim.empty_dict()))\n        assert.is_false(_.is_list { 1, 2, keyed = \"value\" })\n        if vim.fn.has \"nvim-0.10.0\" == 1 then\n            -- meh\n            assert.is_false(_.is_list { 1, 2, nil, 3 })\n        end\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/InstallHandle_spec.lua",
    "content": "local InstallHandle = require \"mason-core.installer.InstallHandle\"\nlocal mock = require \"luassert.mock\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\n\ndescribe(\"InstallHandle ::\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should register spawn handle\", function()\n        local handle = InstallHandle:new(mock.new {})\n        local spawn_handle_change_handler = spy.new()\n        local luv_handle = mock.new {}\n\n        handle:once(\"spawn_handles:change\", spawn_handle_change_handler)\n        handle:register_spawn_handle(luv_handle, 1337, \"tar\", { \"-xvf\", \"file\" })\n\n        assert.same({\n            uv_handle = luv_handle,\n            pid = 1337,\n            cmd = \"tar\",\n            args = { \"-xvf\", \"file\" },\n        }, handle:peek_spawn_handle():get())\n        assert.spy(spawn_handle_change_handler).was_called(1)\n    end)\n\n    it(\"should deregister spawn handle\", function()\n        local handle = InstallHandle:new(mock.new {})\n        local spawn_handle_change_handler = spy.new()\n        local luv_handle1 = mock.new {}\n        local luv_handle2 = mock.new {}\n\n        handle:on(\"spawn_handles:change\", spawn_handle_change_handler)\n        handle:register_spawn_handle(luv_handle1, 42, \"curl\", { \"someurl\" })\n        handle:register_spawn_handle(luv_handle2, 1337, \"tar\", { \"-xvf\", \"file\" })\n\n        assert.is_true(handle:deregister_spawn_handle(luv_handle1))\n        assert.equals(1, #handle.spawn_handles)\n        assert.same({\n            uv_handle = luv_handle2,\n            pid = 1337,\n            cmd = \"tar\",\n            args = { \"-xvf\", \"file\" },\n        }, handle:peek_spawn_handle():get())\n        assert.spy(spawn_handle_change_handler).was_called(3)\n    end)\n\n    it(\"should change state\", function()\n        local handle = InstallHandle:new(mock.new {})\n        local state_change_handler = spy.new()\n\n        handle:once(\"state:change\", state_change_handler)\n        handle:set_state \"QUEUED\"\n\n        assert.equals(\"QUEUED\", handle.state)\n        assert.spy(state_change_handler).was_called(1)\n        assert.spy(state_change_handler).was_called_with(\"QUEUED\", \"IDLE\")\n    end)\n\n    it(\"should send signals to registered handles\", function()\n        local process = require \"mason-core.process\"\n        stub(process, \"kill\")\n        local uv_handle = {}\n        local handle = InstallHandle:new(mock.new {})\n        local kill_handler = spy.new()\n\n        handle:once(\"kill\", kill_handler)\n        handle.state = \"ACTIVE\"\n        handle.spawn_handles = { { uv_handle = uv_handle } }\n        handle:kill(9)\n\n        assert.spy(process.kill).was_called(1)\n        assert.spy(process.kill).was_called_with(uv_handle, 9)\n        assert.spy(kill_handler).was_called(1)\n        assert.spy(kill_handler).was_called_with(9)\n    end)\n\n    it(\"should terminate handle\", function()\n        local process = require \"mason-core.process\"\n        stub(process, \"kill\")\n        local uv_handle1 = {}\n        local uv_handle2 = {}\n        local handle = InstallHandle:new(mock.new {})\n        local kill_handler = spy.new()\n        local terminate_handler = spy.new()\n        local closed_handler = spy.new()\n\n        handle:once(\"kill\", kill_handler)\n        handle:once(\"terminate\", terminate_handler)\n        handle:once(\"closed\", closed_handler)\n        handle.state = \"ACTIVE\"\n        handle.spawn_handles = { { uv_handle = uv_handle2 }, { uv_handle = uv_handle2 } }\n        handle:terminate()\n\n        assert.spy(process.kill).was_called(2)\n        assert.spy(process.kill).was_called_with(uv_handle1, 15)\n        assert.spy(process.kill).was_called_with(uv_handle2, 15)\n        assert.spy(kill_handler).was_called(1)\n        assert.spy(kill_handler).was_called_with(15)\n        assert.spy(terminate_handler).was_called(1)\n        assert.is_true(handle.is_terminated)\n        assert.wait(function()\n            assert.is_true(handle:is_closed())\n            assert.spy(closed_handler).was_called(1)\n        end)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/InstallRunner_spec.lua",
    "content": "local InstallHandle = require \"mason-core.installer.InstallHandle\"\nlocal InstallLocation = require \"mason-core.installer.InstallLocation\"\nlocal InstallRunner = require \"mason-core.installer.InstallRunner\"\nlocal fs = require \"mason-core.fs\"\nlocal match = require \"luassert.match\"\nlocal receipt = require \"mason-core.receipt\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\nlocal Semaphore = require(\"mason-core.async.control\").Semaphore\nlocal a = require \"mason-core.async\"\nlocal registry = require \"mason-registry\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"InstallRunner ::\", function()\n    local dummy = registry.get_package \"dummy\"\n    local dummy2 = registry.get_package \"dummy2\"\n\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n        if dummy:is_installed() then\n            test_helpers.sync_uninstall(dummy)\n        end\n        if dummy2:is_installed() then\n            test_helpers.sync_uninstall(dummy2)\n        end\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    describe(\"locking ::\", function()\n        it(\"should respect semaphore locks\", function()\n            local semaphore = Semaphore:new(1)\n            local location = InstallLocation.global()\n            local dummy_handle = InstallHandle:new(dummy, location)\n            local runner_1 = InstallRunner:new(dummy_handle, semaphore)\n            local runner_2 = InstallRunner:new(InstallHandle:new(dummy2, location), semaphore)\n\n            stub(dummy.spec.source, \"install\", function(ctx)\n                ctx:await(function() end)\n            end)\n            spy.on(dummy2.spec.source, \"install\", function() end)\n\n            local callback1 = spy.new()\n            local callback2 = spy.new()\n            local run = a.scope(function()\n                runner_1:execute({}, callback1)\n                runner_2:execute({}, callback2)\n            end)\n\n            run()\n\n            assert.wait(function()\n                assert.spy(dummy.spec.source.install).was_called(1)\n                assert.spy(dummy2.spec.source.install).was_not_called()\n            end)\n\n            dummy_handle:terminate()\n\n            assert.wait(function()\n                assert.spy(dummy2.spec.source.install).was_called(1)\n            end)\n\n            assert.wait(function()\n                assert.spy(callback1).was_called()\n                assert.spy(callback2).was_called()\n            end)\n        end)\n\n        it(\"should write lockfile\", function()\n            local semaphore = Semaphore:new(1)\n            local location = InstallLocation.global()\n            local dummy_handle = InstallHandle:new(dummy, location)\n            local runner = InstallRunner:new(dummy_handle, semaphore)\n\n            spy.on(fs.async, \"write_file\")\n\n            test_helpers.sync_runner_execute(runner, {})\n\n            assert.wait(function()\n                assert.spy(fs.async.write_file).was_called_with(location:lockfile(dummy.name), vim.fn.getpid())\n            end)\n        end)\n\n        it(\"should abort installation if installation lock exists\", function()\n            local semaphore = Semaphore:new(1)\n            local location = InstallLocation.global()\n            local dummy_handle = InstallHandle:new(dummy, location)\n            local runner = InstallRunner:new(dummy_handle, semaphore)\n\n            stub(fs.async, \"file_exists\")\n            stub(fs.async, \"read_file\")\n            fs.async.file_exists.on_call_with(location:lockfile(dummy.name)).returns(true)\n            fs.async.read_file.on_call_with(location:lockfile(dummy.name)).returns \"1337\"\n\n            local callback = test_helpers.sync_runner_execute(runner, {})\n\n            assert.wait(function()\n                assert.spy(callback).was_called()\n                assert.spy(callback).was_called_with(\n                    false,\n                    \"Lockfile exists, installation is already running in another process (pid: 1337). Run with :MasonInstall --force to bypass.\"\n                )\n            end)\n        end)\n\n        it(\"should not abort installation if installation lock exists with force=true\", function()\n            local semaphore = Semaphore:new(1)\n            local location = InstallLocation.global()\n            local dummy_handle = InstallHandle:new(dummy, location)\n            local runner = InstallRunner:new(dummy_handle, semaphore)\n\n            stub(fs.async, \"file_exists\")\n            stub(fs.async, \"read_file\")\n            fs.async.file_exists.on_call_with(location:lockfile(dummy.name)).returns(true)\n            fs.async.read_file.on_call_with(location:lockfile(dummy.name)).returns \"1337\"\n\n            local callback = test_helpers.sync_runner_execute(runner, { force = true })\n\n            assert.wait(function()\n                assert.spy(callback).was_called()\n                assert.spy(callback).was_called_with(true, match.instanceof(receipt.InstallReceipt))\n            end)\n        end)\n\n        it(\"should release lock after successful installation\", function()\n            local semaphore = Semaphore:new(1)\n            local location = InstallLocation.global()\n            local dummy_handle = InstallHandle:new(dummy, location)\n            local runner = InstallRunner:new(dummy_handle, semaphore)\n            stub(dummy.spec.source, \"install\", function()\n                a.sleep(1000)\n            end)\n\n            local callback = spy.new()\n            runner:execute({}, callback)\n\n            assert.wait(function()\n                assert.is_true(fs.sync.file_exists(location:lockfile(dummy.name)))\n            end)\n            assert.wait(function()\n                assert.spy(callback).was_called_with(true, match.instanceof(receipt.InstallReceipt))\n            end)\n            assert.is_false(fs.sync.file_exists(location:lockfile(dummy.name)))\n        end)\n    end)\n\n    it(\"should initialize install location\", function()\n        local location = InstallLocation.global()\n        local runner = InstallRunner:new(InstallHandle:new(dummy, location), Semaphore:new(1))\n\n        spy.on(location, \"initialize\")\n\n        test_helpers.sync_runner_execute(runner, {})\n\n        assert.wait(function()\n            assert.spy(location.initialize).was_called(1)\n        end)\n    end)\n\n    it(\"should emit failures\", function()\n        local registry_spy = spy.new()\n        local package_spy = spy.new()\n        registry:once(\"package:install:failed\", registry_spy)\n        dummy:once(\"install:failed\", package_spy)\n\n        local location = InstallLocation.global()\n        local handle = InstallHandle:new(dummy, location)\n        local runner = InstallRunner:new(handle, Semaphore:new(1))\n\n        stub(dummy.spec.source, \"install\", function()\n            error(\"I've made a mistake.\", 0)\n        end)\n\n        local callback = test_helpers.sync_runner_execute(runner, {})\n\n        assert.spy(registry_spy).was_called(1)\n        assert.spy(registry_spy).was_called_with(match.is_ref(dummy), \"I've made a mistake.\")\n        assert.spy(package_spy).was_called(1)\n        assert.spy(package_spy).was_called_with \"I've made a mistake.\"\n\n        assert.spy(callback).was_called(1)\n        assert.spy(callback).was_called_with(false, \"I've made a mistake.\")\n    end)\n\n    it(\"should terminate installation\", function()\n        local location = InstallLocation.global()\n        local handle = InstallHandle:new(dummy, location)\n        local runner = InstallRunner:new(handle, Semaphore:new(1))\n\n        local capture = spy.new()\n        stub(dummy.spec.source, \"install\", function()\n            capture(1)\n            handle:terminate()\n            a.sleep(0)\n            capture(2)\n        end)\n\n        local callback = test_helpers.sync_runner_execute(runner, {})\n\n        assert.spy(callback).was_called_with(false, \"Installation was aborted.\")\n        assert.spy(capture).was_called(1)\n        assert.spy(capture).was_called_with(1)\n    end)\n\n    it(\"should write debug logs when debug=true\", function()\n        local location = InstallLocation.global()\n        local handle = InstallHandle:new(dummy, location)\n        local runner = InstallRunner:new(handle, Semaphore:new(1))\n\n        stub(dummy.spec.source, \"install\", function(ctx)\n            ctx.stdio_sink:stdout \"Hello \"\n            ctx.stdio_sink:stderr \"world!\"\n        end)\n\n        local callback = test_helpers.sync_runner_execute(runner, { debug = true })\n\n        assert.spy(callback).was_called_with(true, match.instanceof(receipt.InstallReceipt))\n        assert.is_true(fs.sync.file_exists(location:package \"dummy/mason-debug.log\"))\n        assert.equals(\"Hello world!\", fs.sync.read_file(location:package \"dummy/mason-debug.log\"))\n    end)\n\n    it(\"should not retain installation directory on failure\", function()\n        local location = InstallLocation.global()\n        local handle = InstallHandle:new(dummy, location)\n        local runner = InstallRunner:new(handle, Semaphore:new(1))\n\n        stub(dummy.spec.source, \"install\", function(ctx)\n            ctx.stdio_sink:stderr \"Something will go terribly wrong.\\n\"\n            error(\"This went terribly wrong.\", 0)\n        end)\n\n        local callback = test_helpers.sync_runner_execute(runner, {})\n\n        assert.spy(callback).was_called_with(false, \"This went terribly wrong.\")\n        assert.is_false(fs.sync.dir_exists(location:staging \"dummy\"))\n        assert.is_false(fs.sync.dir_exists(location:package \"dummy\"))\n    end)\n\n    it(\"should retain installation directory on failure and debug=true\", function()\n        local location = InstallLocation.global()\n        local handle = InstallHandle:new(dummy, location)\n        local runner = InstallRunner:new(handle, Semaphore:new(1))\n\n        stub(dummy.spec.source, \"install\", function(ctx)\n            ctx.stdio_sink:stderr \"Something will go terribly wrong.\\n\"\n            error(\"This went terribly wrong.\", 0)\n        end)\n\n        local callback = test_helpers.sync_runner_execute(runner, { debug = true })\n\n        assert.spy(callback).was_called_with(false, \"This went terribly wrong.\")\n        assert.is_true(fs.sync.dir_exists(location:staging \"dummy\"))\n        assert.equals(\n            \"Something will go terribly wrong.\\nThis went terribly wrong.\\n\",\n            fs.sync.read_file(location:staging \"dummy/mason-debug.log\")\n        )\n    end)\n\n    describe(\"receipt ::\", function()\n        it(\"should write receipt\", function()\n            local location = InstallLocation.global()\n            local runner = InstallRunner:new(InstallHandle:new(dummy, location), Semaphore:new(1))\n\n            test_helpers.sync_runner_execute(runner, {})\n\n            local receipt_file = location:package \"dummy/mason-receipt.json\"\n\n            assert.is_true(fs.sync.file_exists(receipt_file))\n            assert.is_true(match.tbl_containing {\n                name = \"dummy\",\n                schema_version = \"2.0\",\n                install_options = match.same {},\n                metrics = match.tbl_containing {\n                    completion_time = match.is_number(),\n                    start_time = match.is_number(),\n                },\n                registry = match.same {\n                    proto = \"lua\",\n                    mod = \"dummy-registry.index\",\n                },\n                source = match.same {\n                    id = \"pkg:mason/dummy@1.0.0\",\n                    type = \"registry+v1\",\n                    raw = {\n                        id = \"pkg:mason/dummy@1.0.0\",\n                    },\n                },\n                links = match.same {\n                    bin = {},\n                    opt = {},\n                    share = {},\n                },\n            }(vim.json.decode(fs.sync.read_file(receipt_file))))\n        end)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compiler_spec.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal compiler = require \"mason-core.installer.compiler\"\nlocal match = require \"luassert.match\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\nlocal util = require \"mason-core.installer.compiler.util\"\n\n---@type InstallerCompiler\nlocal dummy_compiler = {\n    ---@param source RegistryPackageSource\n    ---@param purl Purl\n    ---@param opts PackageInstallOpts\n    parse = function(source, purl, opts)\n        return Result.try(function(try)\n            return {\n                package = purl.name,\n                extra_info = source.extra_info,\n                should_fail = source.should_fail,\n            }\n        end)\n    end,\n    install = function(ctx, source)\n        if source.should_fail then\n            return Result.failure \"This is a failure.\"\n        else\n            return Result.success()\n        end\n    end,\n    get_versions = function()\n        return Result.success { \"v1.0.0\", \"v2.0.0\" }\n    end,\n}\n\ndescribe(\"registry compiler :: parsing\", function()\n    it(\"should parse valid package specs\", function()\n        compiler.register_compiler(\"dummy\", dummy_compiler)\n\n        local result = compiler.parse({\n            schema = \"registry+v1\",\n            source = {\n                id = \"pkg:dummy/package-name@v1.2.3\",\n                extra_info = \"here\",\n            },\n        }, {})\n        local parsed = result:get_or_nil()\n\n        assert.is_true(result:is_success())\n        assert.is_true(match.is_ref(dummy_compiler)(parsed.compiler))\n        assert.same({\n            name = \"package-name\",\n            scheme = \"pkg\",\n            type = \"dummy\",\n            version = \"v1.2.3\",\n        }, parsed.purl)\n        assert.same({\n            id = \"pkg:dummy/package-name@v1.2.3\",\n            package = \"package-name\",\n            extra_info = \"here\",\n        }, parsed.source)\n    end)\n\n    it(\"should keep unmapped fields\", function()\n        compiler.register_compiler(\"dummy\", dummy_compiler)\n\n        local result = compiler.parse({\n            schema = \"registry+v1\",\n            source = {\n                id = \"pkg:dummy/package-name@v1.2.3\",\n                bin = \"node:server.js\",\n            },\n        }, {})\n        local parsed = result:get_or_nil()\n\n        assert.is_true(result:is_success())\n        assert.same({\n            id = \"pkg:dummy/package-name@v1.2.3\",\n            package = \"package-name\",\n            bin = \"node:server.js\",\n        }, parsed.source)\n    end)\n\n    it(\"should reject incompatible schema versions\", function()\n        compiler.register_compiler(\"dummy\", dummy_compiler)\n\n        local result = compiler.parse({\n            schema = \"registry+v1337\",\n            source = {\n                id = \"pkg:dummy/package-name@v1.2.3\",\n            },\n        }, {})\n        assert.same(\n            Result.failure [[Current version of mason.nvim is not capable of parsing package schema version \"registry+v1337\".]],\n            result\n        )\n    end)\n\n    it(\"should use requested version\", function()\n        compiler.register_compiler(\"dummy\", dummy_compiler)\n\n        local result = compiler.parse({\n            schema = \"registry+v1\",\n            source = {\n                id = \"pkg:dummy/package-name@v1.2.3\",\n            },\n        }, { version = \"v2.0.0\" })\n\n        assert.is_true(result:is_success())\n        local parsed = result:get_or_nil()\n\n        assert.same({\n            name = \"package-name\",\n            scheme = \"pkg\",\n            type = \"dummy\",\n            version = \"v2.0.0\",\n        }, parsed.purl)\n    end)\n\n    it(\"should handle PLATFORM_UNSUPPORTED\", function()\n        compiler.register_compiler(\"dummy\", dummy_compiler)\n\n        local result = compiler.compile_installer({\n            schema = \"registry+v1\",\n            source = {\n                id = \"pkg:dummy/package-name@v1.2.3\",\n                supported_platforms = { \"VIC64\" },\n            },\n        }, { version = \"v2.0.0\" })\n\n        assert.same(Result.failure \"The current platform is unsupported.\", result)\n    end)\n\n    it(\"should error upon parsing failures\", function()\n        compiler.register_compiler(\"dummy\", dummy_compiler)\n\n        local result = compiler.compile_installer({\n            schema = \"registry+v1\",\n            source = {\n                id = \"pkg:dummy/package-name@v1.2.3\",\n                supported_platforms = { \"VIC64\" },\n            },\n        }, { version = \"v2.0.0\" })\n\n        assert.same(Result.failure \"The current platform is unsupported.\", result)\n    end)\nend)\n\ndescribe(\"registry compiler :: compiling\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should run compiled installer function successfully\", function()\n        compiler.register_compiler(\"dummy\", dummy_compiler)\n        spy.on(dummy_compiler, \"get_versions\")\n\n        ---@type PackageInstallOpts\n        local opts = {}\n\n        local result = compiler.compile_installer({\n            schema = \"registry+v1\",\n            source = {\n                id = \"pkg:dummy/package-name@v1.2.3\",\n            },\n        }, opts)\n\n        assert.is_true(result:is_success())\n        local installer_fn = result:get_or_throw()\n\n        local ctx = test_helpers.create_context()\n        local installer_result = ctx:execute(installer_fn)\n\n        assert.same(Result.success(), installer_result)\n        assert.spy(dummy_compiler.get_versions).was_not_called()\n    end)\n\n    it(\"should ensure valid version\", function()\n        compiler.register_compiler(\"dummy\", dummy_compiler)\n        spy.on(dummy_compiler, \"get_versions\")\n\n        ---@type PackageInstallOpts\n        local opts = { version = \"v2.0.0\" }\n\n        local result = compiler.compile_installer({\n            schema = \"registry+v1\",\n            source = {\n                id = \"pkg:dummy/package-name@v1.2.3\",\n            },\n        }, opts)\n\n        assert.is_true(result:is_success())\n        local installer_fn = result:get_or_throw()\n\n        local ctx = test_helpers.create_context { install_opts = opts }\n        local installer_result = ctx:execute(installer_fn)\n        assert.same(Result.success(), installer_result)\n\n        assert.spy(dummy_compiler.get_versions).was_called(1)\n        assert.spy(dummy_compiler.get_versions).was_called_with({\n            name = \"package-name\",\n            scheme = \"pkg\",\n            type = \"dummy\",\n            version = \"v2.0.0\",\n        }, {\n            id = \"pkg:dummy/package-name@v1.2.3\",\n        })\n    end)\n\n    it(\"should reject invalid version\", function()\n        compiler.register_compiler(\"dummy\", dummy_compiler)\n        spy.on(dummy_compiler, \"get_versions\")\n\n        ---@type PackageInstallOpts\n        local opts = { version = \"v13.3.7\" }\n\n        local result = compiler.compile_installer({\n            schema = \"registry+v1\",\n            source = {\n                id = \"pkg:dummy/package-name@v1.2.3\",\n            },\n        }, opts)\n\n        assert.is_true(result:is_success())\n        local installer_fn = result:get_or_throw()\n\n        local ctx = test_helpers.create_context { install_opts = opts }\n        local err = assert.has_error(function()\n            ctx:execute(installer_fn):get_or_throw()\n        end)\n\n        assert.equals([[Version \"v13.3.7\" is not available.]], err)\n        assert.spy(dummy_compiler.get_versions).was_called(1)\n        assert.spy(dummy_compiler.get_versions).was_called_with({\n            name = \"package-name\",\n            scheme = \"pkg\",\n            type = \"dummy\",\n            version = \"v13.3.7\",\n        }, {\n            id = \"pkg:dummy/package-name@v1.2.3\",\n        })\n    end)\n\n    it(\"should raise errors upon installer failures\", function()\n        compiler.register_compiler(\"dummy\", dummy_compiler)\n\n        ---@type PackageInstallOpts\n        local opts = {}\n\n        local result = compiler.compile_installer({\n            schema = \"registry+v1\",\n            source = {\n                id = \"pkg:dummy/package-name@v1.2.3\",\n                should_fail = true,\n            },\n        }, opts)\n\n        assert.is_true(result:is_success())\n        local installer_fn = result:get_or_nil()\n\n        local ctx = test_helpers.create_context()\n        local err = assert.has_error(function()\n            ctx:execute(installer_fn):get_or_throw()\n        end)\n        assert.equals(\"This is a failure.\", err)\n    end)\n\n    it(\"should register links\", function()\n        compiler.register_compiler(\"dummy\", dummy_compiler)\n        local link = require \"mason-core.installer.compiler.link\"\n        stub(link, \"bin\", mockx.returns(Result.success()))\n        stub(link, \"share\", mockx.returns(Result.success()))\n        stub(link, \"opt\", mockx.returns(Result.success()))\n\n        local spec = {\n            schema = \"registry+v1\",\n            source = {\n                id = \"pkg:dummy/package-name@v1.2.3\",\n            },\n            bin = { [\"exec\"] = \"exec\" },\n            opt = { [\"opt/\"] = \"opt/\" },\n            share = { [\"share/\"] = \"share/\" },\n        }\n        ---@type PackageInstallOpts\n        local opts = {}\n\n        local result = compiler.compile_installer(spec, opts)\n\n        assert.is_true(result:is_success())\n        local installer_fn = result:get_or_nil()\n\n        local ctx = test_helpers.create_context()\n        local installer_result = ctx:execute(installer_fn)\n        assert.is_true(installer_result:is_success())\n\n        for _, spy in ipairs { link.bin, link.share, link.opt } do\n            assert.spy(spy).was_called(1)\n            assert.spy(spy).was_called_with(match.is_ref(ctx), spec, {\n                scheme = \"pkg\",\n                type = \"dummy\",\n                name = \"package-name\",\n                version = \"v1.2.3\",\n            }, {\n                id = \"pkg:dummy/package-name@v1.2.3\",\n                package = \"package-name\",\n            })\n        end\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/cargo_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal cargo = require \"mason-core.installer.compiler.compilers.cargo\"\nlocal providers = require \"mason-core.providers\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:cargo/crate-name@1.4.3\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"cargo compiler :: parsing\", function()\n    it(\"should parse package\", function()\n        assert.same(\n            Result.success {\n                crate = \"crate-name\",\n                version = \"1.4.3\",\n                features = nil,\n                locked = true,\n                git = nil,\n            },\n            cargo.parse({}, purl())\n        )\n    end)\n\n    it(\"should respect repository_url qualifier\", function()\n        assert.same(\n            Result.success {\n                crate = \"crate-name\",\n                version = \"1.4.3\",\n                features = nil,\n                locked = true,\n                git = { url = \"https://github.com/crate-org/crate-name\", rev = false },\n            },\n            cargo.parse({}, purl { qualifiers = { repository_url = \"https://github.com/crate-org/crate-name\" } })\n        )\n    end)\n\n    it(\"should respect repository_url qualifier with rev=true qualifier\", function()\n        assert.same(\n            Result.success {\n                crate = \"crate-name\",\n                version = \"1.4.3\",\n                features = nil,\n                locked = true,\n                git = { url = \"https://github.com/crate-org/crate-name\", rev = true },\n            },\n            cargo.parse(\n                {},\n                purl { qualifiers = { repository_url = \"https://github.com/crate-org/crate-name\", rev = \"true\" } }\n            )\n        )\n    end)\n\n    it(\"should respect features qualifier\", function()\n        assert.same(\n            Result.success {\n                crate = \"crate-name\",\n                version = \"1.4.3\",\n                features = \"lsp,cli\",\n                locked = true,\n                git = nil,\n            },\n            cargo.parse({}, purl { qualifiers = { features = \"lsp,cli\" } })\n        )\n    end)\n\n    it(\"should respect locked qualifier\", function()\n        assert.same(\n            Result.success {\n                crate = \"crate-name\",\n                version = \"1.4.3\",\n                features = nil,\n                locked = false,\n                git = nil,\n            },\n            cargo.parse({}, purl { qualifiers = { locked = \"false\" } })\n        )\n    end)\nend)\n\ndescribe(\"cargo compiler :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install cargo packages\", function()\n        local ctx = test_helpers.create_context()\n        local manager = require \"mason-core.installer.managers.cargo\"\n        stub(manager, \"install\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return cargo.install(ctx, {\n                crate = \"crate-name\",\n                version = \"1.2.0\",\n                features = nil,\n                locked = true,\n                git = nil,\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(manager.install).was_called(1)\n        assert.spy(manager.install).was_called_with(\"crate-name\", \"1.2.0\", {\n            git = nil,\n            features = nil,\n            locked = true,\n        })\n    end)\nend)\n\ndescribe(\"cargo compiler :: versions\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should recognize github cargo source\", function()\n        stub(providers.github, \"get_all_tags\", function()\n            return Result.success { \"1.0.0\", \"2.0.0\", \"3.0.0\" }\n        end)\n\n        local result = cargo.get_versions(purl {\n            qualifiers = {\n                repository_url = \"https://github.com/rust-lang/rust-analyzer\",\n            },\n        })\n\n        assert.is_true(result:is_success())\n        assert.same({ \"1.0.0\", \"2.0.0\", \"3.0.0\" }, result:get_or_throw())\n        assert.spy(providers.github.get_all_tags).was_called(1)\n        assert.spy(providers.github.get_all_tags).was_called_with \"rust-lang/rust-analyzer\"\n    end)\n\n    it(\"should not provide git commit SHAs\", function()\n        local result = cargo.get_versions(purl {\n            qualifiers = {\n                repository_url = \"https://github.com/rust-lang/rust-analyzer\",\n                rev = \"true\",\n            },\n        })\n\n        assert.is_false(result:is_success())\n        assert.equals(\"Unable to retrieve commit SHAs.\", result:err_or_nil())\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/composer_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal composer = require \"mason-core.installer.compiler.compilers.composer\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:composer/vendor/package@2.0.0\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"composer compiler :: parsing\", function()\n    it(\"should parse package\", function()\n        assert.same(\n            Result.success {\n                package = \"vendor/package\",\n                version = \"2.0.0\",\n            },\n            composer.parse({}, purl())\n        )\n    end)\nend)\n\ndescribe(\"composer compiler :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install composer packages\", function()\n        local ctx = test_helpers.create_context()\n        local manager = require \"mason-core.installer.managers.composer\"\n        stub(manager, \"install\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return composer.install(ctx, {\n                package = \"vendor/package\",\n                version = \"1.2.0\",\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(manager.install).was_called(1)\n        assert.spy(manager.install).was_called_with(\"vendor/package\", \"1.2.0\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/gem_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal gem = require \"mason-core.installer.compiler.compilers.gem\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:gem/package@1.2.0\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"gem compiler :: parsing\", function()\n    it(\"should parse package\", function()\n        assert.same(\n            Result.success {\n                package = \"package\",\n                version = \"1.2.0\",\n                extra_packages = { \"extra\" },\n            },\n            gem.parse({ extra_packages = { \"extra\" } }, purl())\n        )\n    end)\nend)\n\ndescribe(\"gem compiler :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install gem packages\", function()\n        local ctx = test_helpers.create_context()\n        local manager = require \"mason-core.installer.managers.gem\"\n        stub(manager, \"install\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return gem.install(ctx, {\n                package = \"package\",\n                version = \"5.2.0\",\n                extra_packages = { \"extra\" },\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(manager.install).was_called(1)\n        assert.spy(manager.install).was_called_with(\"package\", \"5.2.0\", { extra_packages = { \"extra\" } })\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/generic/build_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal generic = require \"mason-core.installer.compiler.compilers.generic\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:generic/namespace/name@v1.2.0\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"generic compiler :: build :: parsing\", function()\n    it(\"should parse single build target\", function()\n        assert.same(\n            Result.success {\n                build = {\n                    run = \"make build\",\n                    env = {\n                        SOME_VALUE = \"here\",\n                    },\n                },\n            },\n            generic.parse({\n                build = {\n                    run = \"make build\",\n                    env = {\n                        SOME_VALUE = \"here\",\n                    },\n                },\n            }, purl())\n        )\n    end)\n\n    it(\"should coalesce build target\", function()\n        assert.same(\n            Result.success {\n                build = {\n                    target = \"linux_arm64\",\n                    run = \"make build\",\n                    env = {\n                        LINUX = \"yes\",\n                    },\n                },\n            },\n            generic.parse({\n                build = {\n                    {\n                        target = \"linux_arm64\",\n                        run = \"make build\",\n                        env = {\n                            LINUX = \"yes\",\n                        },\n                    },\n                    {\n                        target = \"win_arm64\",\n                        run = \"make build\",\n                        env = {\n                            WINDOWS = \"yes\",\n                        },\n                    },\n                },\n            }, purl(), { target = \"linux_arm64\" })\n        )\n    end)\n\n    it(\"should interpolate environment\", function()\n        assert.same(\n            Result.success {\n                build = {\n                    run = \"make build\",\n                    env = {\n                        LINUX = \"2023-04-18\",\n                    },\n                },\n            },\n            generic.parse(\n                {\n                    build = {\n                        run = \"make build\",\n                        env = {\n                            LINUX = \"{{version}}\",\n                        },\n                    },\n                },\n                purl { version = \"2023-04-18\" },\n                {\n                    target = \"linux_arm64\",\n                }\n            )\n        )\n    end)\n\n    it(\"should check supported platforms\", function()\n        assert.same(\n            Result.failure \"PLATFORM_UNSUPPORTED\",\n            generic.parse(\n                {\n                    build = {\n                        {\n                            target = \"win_arm64\",\n                            run = \"make build\",\n                            env = {\n                                WINDOWS = \"yes\",\n                            },\n                        },\n                    },\n                },\n                purl(),\n                {\n                    target = \"linux_x64\",\n                }\n            )\n        )\n    end)\nend)\n\ndescribe(\"generic compiler :: build :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install\", function()\n        local ctx = test_helpers.create_context()\n        local common = require \"mason-core.installer.managers.common\"\n        stub(common, \"run_build_instruction\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return generic.install(ctx, {\n                build = {\n                    run = \"make\",\n                    env = { VALUE = \"here\" },\n                },\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(common.run_build_instruction).was_called(1)\n        assert.spy(common.run_build_instruction).was_called_with {\n            run = \"make\",\n            env = { VALUE = \"here\" },\n        }\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/generic/download_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal generic = require \"mason-core.installer.compiler.compilers.generic\"\nlocal match = require \"luassert.match\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:generic/namespace/name@v1.2.0\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"generic compiler :: download :: parsing\", function()\n    it(\"should parse single download target\", function()\n        assert.same(\n            Result.success {\n                downloads = {\n                    {\n                        out_file = \"name.tar.gz\",\n                        download_url = \"https://getpackage.org/downloads/1.2.0/name.tar.gz\",\n                    },\n                },\n                download = {\n                    files = {\n                        [\"name.tar.gz\"] = [[https://getpackage.org/downloads/1.2.0/name.tar.gz]],\n                    },\n                },\n            },\n            generic.parse({\n                download = {\n                    files = {\n                        [\"name.tar.gz\"] = [[https://getpackage.org/downloads/{{version | strip_prefix \"v\"}}/name.tar.gz]],\n                    },\n                },\n            }, purl())\n        )\n    end)\n\n    it(\"should coalesce download target\", function()\n        assert.same(\n            Result.success {\n                downloads = {\n                    {\n                        out_file = \"name.tar.gz\",\n                        download_url = \"https://getpackage.org/downloads/linux-aarch64/1.2.0/name.tar.gz\",\n                    },\n                },\n                download = {\n                    target = \"linux_arm64\",\n                    files = {\n                        [\"name.tar.gz\"] = [[https://getpackage.org/downloads/linux-aarch64/1.2.0/name.tar.gz]],\n                    },\n                },\n            },\n            generic.parse({\n                download = {\n                    {\n                        target = \"linux_arm64\",\n                        files = {\n                            [\"name.tar.gz\"] = [[https://getpackage.org/downloads/linux-aarch64/{{version | strip_prefix \"v\"}}/name.tar.gz]],\n                        },\n                    },\n                    {\n                        target = \"win_arm64\",\n                        files = {\n                            [\"name.tar.gz\"] = [[https://getpackage.org/downloads/win-aarch64/{{version | strip_prefix \"v\"}}/name.tar.gz]],\n                        },\n                    },\n                },\n            }, purl(), { target = \"linux_arm64\" })\n        )\n    end)\n\n    it(\"should check supported platforms\", function()\n        assert.same(\n            Result.failure \"PLATFORM_UNSUPPORTED\",\n            generic.parse(\n                {\n                    download = {\n                        {\n                            target = \"win_arm64\",\n                            files = {\n                                [\"name.tar.gz\"] = [[https://getpackage.org/downloads/win-aarch64/{{version | strip_prefix \"v\"}}/name.tar.gz]],\n                            },\n                        },\n                    },\n                },\n                purl(),\n                {\n                    target = \"linux_arm64\",\n                }\n            )\n        )\n    end)\nend)\n\ndescribe(\"generic compiler :: download :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install generic packages\", function()\n        local ctx = test_helpers.create_context()\n        local common = require \"mason-core.installer.managers.common\"\n        stub(common, \"download_files\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return generic.install(ctx, {\n                downloads = {\n                    {\n                        out_file = \"name.tar.gz\",\n                        download_url = \"https://getpackage.org/downloads/linux-aarch64/1.2.0/name.tar.gz\",\n                    },\n                },\n                download = {\n                    target = \"linux_arm64\",\n                    files = {\n                        [\"name.tar.gz\"] = [[https://getpackage.org/downloads/linux-aarch64/1.2.0/name.tar.gz]],\n                    },\n                },\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(common.download_files).was_called(1)\n        assert.spy(common.download_files).was_called_with(match.is_ref(ctx), {\n            {\n                out_file = \"name.tar.gz\",\n                download_url = \"https://getpackage.org/downloads/linux-aarch64/1.2.0/name.tar.gz\",\n            },\n        })\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/github/build_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal github = require \"mason-core.installer.compiler.compilers.github\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:github/namespace/name@2023-03-09\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"github compiler :: build :: parsing\", function()\n    it(\"should parse build source\", function()\n        assert.same(\n            Result.success {\n                build = {\n                    run = [[npm install && npm run compile]],\n                    env = {},\n                },\n                repo = \"https://github.com/namespace/name.git\",\n                rev = \"2023-03-09\",\n            },\n            github.parse({\n                build = {\n                    run = [[npm install && npm run compile]],\n                },\n            }, purl())\n        )\n    end)\n\n    it(\"should parse build source with multiple targets\", function()\n        assert.same(\n            Result.success {\n                build = {\n                    target = \"win_x64\",\n                    run = [[npm install]],\n                    env = {},\n                },\n                repo = \"https://github.com/namespace/name.git\",\n                rev = \"2023-03-09\",\n            },\n            github.parse({\n                build = {\n                    {\n                        target = \"linux_arm64\",\n                        run = [[npm install && npm run compile]],\n                    },\n                    {\n                        target = \"win_x64\",\n                        run = [[npm install]],\n                    },\n                },\n            }, purl(), { target = \"win_x64\" })\n        )\n    end)\nend)\n\ndescribe(\"github compiler :: build :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install github build sources\", function()\n        local ctx = test_helpers.create_context()\n        local std = require \"mason-core.installer.managers.std\"\n        local common = require \"mason-core.installer.managers.common\"\n        stub(std, \"clone\", mockx.returns(Result.success()))\n        stub(common, \"run_build_instruction\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return github.install(ctx, {\n                repo = \"namespace/name\",\n                rev = \"2023-03-09\",\n                build = {\n                    run = [[npm install && npm run compile]],\n                    env = {\n                        SOME_VALUE = \"here\",\n                    },\n                },\n            }, purl())\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(std.clone).was_called(1)\n        assert.spy(std.clone).was_called_with(\"namespace/name\", { rev = \"2023-03-09\" })\n        assert.spy(common.run_build_instruction).was_called(1)\n        assert.spy(common.run_build_instruction).was_called_with {\n            run = [[npm install && npm run compile]],\n            env = {\n                SOME_VALUE = \"here\",\n            },\n        }\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/github/release_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal common = require \"mason-core.installer.managers.common\"\nlocal compiler = require \"mason-core.installer.compiler\"\nlocal github = require \"mason-core.installer.compiler.compilers.github\"\nlocal match = require \"luassert.match\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:github/namespace/name@2023-03-09\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"github compiler :: release :: parsing\", function()\n    it(\"should parse release asset source\", function()\n        assert.same(\n            Result.success {\n                repo = \"namespace/name\",\n                asset = {\n                    file = \"file-2023-03-09.jar\",\n                },\n                downloads = {\n                    {\n                        out_file = \"file-2023-03-09.jar\",\n                        download_url = \"https://github.com/namespace/name/releases/download/2023-03-09/file-2023-03-09.jar\",\n                    },\n                },\n            },\n            github.parse({\n                asset = {\n                    file = \"file-{{version}}.jar\",\n                },\n            }, purl())\n        )\n    end)\n\n    it(\"should parse release asset source with multiple targets\", function()\n        assert.same(\n            Result.success {\n                repo = \"namespace/name\",\n                asset = {\n                    target = \"linux_x64\",\n                    file = \"file-linux-amd64-2023-03-09.tar.gz\",\n                },\n                downloads = {\n                    {\n                        out_file = \"file-linux-amd64-2023-03-09.tar.gz\",\n                        download_url = \"https://github.com/namespace/name/releases/download/2023-03-09/file-linux-amd64-2023-03-09.tar.gz\",\n                    },\n                },\n            },\n            github.parse({\n                asset = {\n                    {\n                        target = \"win_arm\",\n                        file = \"file-win-arm-{{version}}.zip\",\n                    },\n                    {\n                        target = \"linux_x64\",\n                        file = \"file-linux-amd64-{{version}}.tar.gz\",\n                    },\n                },\n            }, purl(), { target = \"linux_x64\" })\n        )\n    end)\n\n    it(\"should parse release asset source with output to different directory\", function()\n        assert.same(\n            Result.success {\n                repo = \"namespace/name\",\n                asset = {\n                    file = \"out-dir/file-linux-amd64-2023-03-09.tar.gz\",\n                },\n                downloads = {\n                    {\n                        out_file = \"out-dir/file-linux-amd64-2023-03-09.tar.gz\",\n                        download_url = \"https://github.com/namespace/name/releases/download/2023-03-09/file-linux-amd64-2023-03-09.tar.gz\",\n                    },\n                },\n            },\n            github.parse({\n                asset = {\n                    file = \"file-linux-amd64-{{version}}.tar.gz:out-dir/\",\n                },\n            }, purl(), { target = \"linux_x64\" })\n        )\n    end)\n\n    it(\"should expand returned asset.file to point to out_file\", function()\n        assert.same(\n            Result.success {\n                repo = \"namespace/name\",\n                asset = {\n                    file = {\n                        \"out-dir/linux-amd64-2023-03-09.tar.gz\",\n                        \"LICENSE.txt\",\n                        \"README.md\",\n                    },\n                },\n                downloads = {\n                    {\n                        out_file = \"out-dir/linux-amd64-2023-03-09.tar.gz\",\n                        download_url = \"https://github.com/namespace/name/releases/download/2023-03-09/linux-amd64-2023-03-09.tar.gz\",\n                    },\n                    {\n                        out_file = \"LICENSE.txt\",\n                        download_url = \"https://github.com/namespace/name/releases/download/2023-03-09/license\",\n                    },\n                    {\n                        out_file = \"README.md\",\n                        download_url = \"https://github.com/namespace/name/releases/download/2023-03-09/README.md\",\n                    },\n                },\n            },\n            github.parse({\n                asset = {\n                    file = {\n                        \"linux-amd64-{{version}}.tar.gz:out-dir/\",\n                        \"license:LICENSE.txt\",\n                        \"README.md\",\n                    },\n                },\n            }, purl(), { target = \"linux_x64\" })\n        )\n    end)\n\n    it(\"should interpolate asset table\", function()\n        assert.same(\n            Result.success {\n                repo = \"namespace/name\",\n                asset = {\n                    file = \"linux-amd64-2023-03-09.tar.gz\",\n                    bin = \"linux-amd64-2023-03-09\",\n                },\n                downloads = {\n                    {\n                        out_file = \"linux-amd64-2023-03-09.tar.gz\",\n                        download_url = \"https://github.com/namespace/name/releases/download/2023-03-09/linux-amd64-2023-03-09.tar.gz\",\n                    },\n                },\n            },\n            github.parse({\n                asset = {\n                    file = \"linux-amd64-{{version}}.tar.gz\",\n                    bin = \"linux-amd64-{{version}}\",\n                },\n            }, purl(), { target = \"linux_x64\" })\n        )\n    end)\n\n    it(\"should parse build source\", function()\n        assert.same(\n            Result.success {\n                build = {\n                    run = [[npm install && npm run compile]],\n                    env = {},\n                },\n                repo = \"https://github.com/namespace/name.git\",\n                rev = \"2023-03-09\",\n            },\n            github.parse({\n                build = {\n                    run = [[npm install && npm run compile]],\n                },\n            }, purl())\n        )\n    end)\n\n    it(\"should parse build source with multiple targets\", function()\n        assert.same(\n            Result.success {\n                build = {\n                    target = \"win_x64\",\n                    run = [[npm install]],\n                    env = {},\n                },\n                repo = \"https://github.com/namespace/name.git\",\n                rev = \"2023-03-09\",\n            },\n            github.parse({\n                build = {\n                    {\n                        target = \"linux_arm64\",\n                        run = [[npm install && npm run compile]],\n                    },\n                    {\n                        target = \"win_x64\",\n                        run = [[npm install]],\n                    },\n                },\n            }, purl(), { target = \"win_x64\" })\n        )\n    end)\n\n    it(\"should upsert version overrides\", function()\n        local result = compiler.parse({\n            schema = \"registry+v1\",\n            source = {\n                id = \"pkg:github/owner/repo@1.2.3\",\n                asset = {\n                    {\n                        target = \"darwin_x64\",\n                        file = \"asset.tar.gz\",\n                    },\n                },\n                version_overrides = {\n                    {\n                        constraint = \"semver:<=1.0.0\",\n                        id = \"pkg:github/owner/repo@1.0.0\",\n                        asset = {\n                            {\n                                target = \"darwin_x64\",\n                                file = \"old-asset.tar.gz\",\n                            },\n                        },\n                    },\n                },\n            },\n        }, { version = \"1.0.0\", target = \"darwin_x64\" })\n        local parsed = result:get_or_nil()\n\n        assert.is_true(result:is_success())\n        assert.same({\n            id = \"pkg:github/owner/repo@1.0.0\",\n            asset = {\n                target = \"darwin_x64\",\n                file = \"old-asset.tar.gz\",\n            },\n            downloads = {\n                {\n                    download_url = \"https://github.com/owner/repo/releases/download/1.0.0/old-asset.tar.gz\",\n                    out_file = \"old-asset.tar.gz\",\n                },\n            },\n            repo = \"owner/repo\",\n        }, parsed.source)\n    end)\n\n    it(\"should override source if version override provides its own purl id\", function()\n        local result = compiler.parse({\n            schema = \"registry+v1\",\n            source = {\n                id = \"pkg:github/owner/repo@1.2.3\",\n                asset = {\n                    file = \"asset.tar.gz\",\n                },\n                version_overrides = {\n                    {\n                        constraint = \"semver:<=1.0.0\",\n                        id = \"pkg:npm/old-package\",\n                    },\n                },\n            },\n        }, { version = \"1.0.0\", target = \"darwin_x64\" })\n\n        assert.is_true(result:is_success())\n        local parsed = result:get_or_throw()\n        assert.same({\n            type = \"npm\",\n            scheme = \"pkg\",\n            name = \"old-package\",\n            version = \"1.0.0\",\n        }, parsed.purl)\n    end)\nend)\n\ndescribe(\"github compiler :: release :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install github release assets\", function()\n        local ctx = test_helpers.create_context()\n        local std = require \"mason-core.installer.managers.std\"\n        stub(std, \"download_file\", mockx.returns(Result.success()))\n        stub(std, \"unpack\", mockx.returns(Result.success()))\n        stub(common, \"download_files\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return github.install(ctx, {\n                repo = \"namespace/name\",\n                asset = {\n                    file = \"file-linux-amd64-2023-03-09.tar.gz\",\n                },\n                downloads = {\n                    {\n                        out_file = \"file-linux-amd64-2023-03-09.tar.gz\",\n                        download_url = \"https://github.com/namespace/name/releases/download/2023-03-09/file-linux-amd64-2023-03-09.tar.gz\",\n                    },\n                    {\n                        out_file = \"another-file-linux-amd64-2023-03-09.tar.gz\",\n                        download_url = \"https://github.com/namespace/name/releases/download/2023-03-09/another-file-linux-amd64-2023-03-09.tar.gz\",\n                    },\n                },\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(common.download_files).was_called(1)\n        assert.spy(common.download_files).was_called_with(match.is_ref(ctx), {\n            {\n                out_file = \"file-linux-amd64-2023-03-09.tar.gz\",\n                download_url = \"https://github.com/namespace/name/releases/download/2023-03-09/file-linux-amd64-2023-03-09.tar.gz\",\n            },\n            {\n                out_file = \"another-file-linux-amd64-2023-03-09.tar.gz\",\n                download_url = \"https://github.com/namespace/name/releases/download/2023-03-09/another-file-linux-amd64-2023-03-09.tar.gz\",\n            },\n        })\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/golang_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal golang = require \"mason-core.installer.compiler.compilers.golang\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:golang/namespace/package@v1.5.0\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"golang compiler :: parsing\", function()\n    it(\"should parse package\", function()\n        assert.same(\n            Result.success {\n                package = \"namespace/package\",\n                version = \"v1.5.0\",\n                extra_packages = { \"extra\" },\n            },\n            golang.parse({ extra_packages = { \"extra\" } }, purl())\n        )\n    end)\nend)\n\ndescribe(\"golang compiler :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install golang packages\", function()\n        local ctx = test_helpers.create_context()\n        local manager = require \"mason-core.installer.managers.golang\"\n        stub(manager, \"install\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return golang.install(ctx, {\n                package = \"namespace/package\",\n                version = \"v1.5.0\",\n                extra_packages = { \"extra\" },\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(manager.install).was_called(1)\n        assert.spy(manager.install).was_called_with(\"namespace/package\", \"v1.5.0\", { extra_packages = { \"extra\" } })\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/luarocks_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal luarocks = require \"mason-core.installer.compiler.compilers.luarocks\"\nlocal match = require \"luassert.match\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:luarocks/namespace/name@1.0.0\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"luarocks compiler :: parsing\", function()\n    it(\"should parse package\", function()\n        assert.same(\n            Result.success {\n                package = \"namespace/name\",\n                version = \"1.0.0\",\n                server = nil,\n                dev = false,\n            },\n            luarocks.parse({}, purl())\n        )\n    end)\n\n    it(\"should parse package dev flag\", function()\n        assert.same(\n            Result.success {\n                package = \"namespace/name\",\n                version = \"1.0.0\",\n                server = nil,\n                dev = true,\n            },\n            luarocks.parse({}, purl { qualifiers = { dev = \"true\" } })\n        )\n    end)\n\n    it(\"should parse package server flag\", function()\n        assert.same(\n            Result.success {\n                package = \"namespace/name\",\n                version = \"1.0.0\",\n                server = \"https://luarocks.org/dev\",\n                dev = false,\n            },\n            luarocks.parse({}, purl { qualifiers = { repository_url = \"https://luarocks.org/dev\" } })\n        )\n    end)\nend)\n\ndescribe(\"luarocks compiler :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install luarocks packages\", function()\n        local ctx = test_helpers.create_context()\n        local manager = require \"mason-core.installer.managers.luarocks\"\n        local ret_val = Result.success()\n        stub(manager, \"install\", mockx.returns(ret_val))\n\n        local result = ctx:execute(function()\n            return luarocks.install(ctx, {\n                package = \"namespace/name\",\n                version = \"1.0.0\",\n                server = \"https://luarocks.org/dev\",\n                dev = false,\n            })\n        end)\n\n        assert.is_true(match.is_ref(ret_val)(result))\n        assert.spy(manager.install).was_called(1)\n        assert.spy(manager.install).was_called_with(\"namespace/name\", \"1.0.0\", {\n            dev = false,\n            server = \"https://luarocks.org/dev\",\n        })\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/npm_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal npm = require \"mason-core.installer.compiler.compilers.npm\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:npm/%40namespace/package@v1.5.0\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"npm compiler :: parsing\", function()\n    it(\"should parse package\", function()\n        assert.same(\n            Result.success {\n                package = \"@namespace/package\",\n                version = \"v1.5.0\",\n                extra_packages = { \"extra\" },\n            },\n            npm.parse({ extra_packages = { \"extra\" } }, purl())\n        )\n    end)\nend)\n\ndescribe(\"npm compiler :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install npm packages\", function()\n        local ctx = test_helpers.create_context()\n        local manager = require \"mason-core.installer.managers.npm\"\n        stub(manager, \"init\", mockx.returns(Result.success()))\n        stub(manager, \"install\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return npm.install(ctx, {\n                package = \"@namespace/package\",\n                version = \"v1.5.0\",\n                extra_packages = { \"extra\" },\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(manager.init).was_called(1)\n        assert.spy(manager.install).was_called(1)\n        assert.spy(manager.install).was_called_with(\"@namespace/package\", \"v1.5.0\", { extra_packages = { \"extra\" } })\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/nuget_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal nuget = require \"mason-core.installer.compiler.compilers.nuget\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:nuget/package@2.2.0\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"nuget compiler :: parsing\", function()\n    it(\"should parse package\", function()\n        assert.same(\n            Result.success {\n                package = \"package\",\n                version = \"2.2.0\",\n            },\n            nuget.parse({}, purl())\n        )\n    end)\nend)\n\ndescribe(\"nuget compiler :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install nuget packages\", function()\n        local ctx = test_helpers.create_context()\n        local manager = require \"mason-core.installer.managers.nuget\"\n        stub(manager, \"install\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return nuget.install(ctx, {\n                package = \"package\",\n                version = \"1.5.0\",\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(manager.install).was_called(1)\n        assert.spy(manager.install).was_called_with(\"package\", \"1.5.0\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/opam_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal opam = require \"mason-core.installer.compiler.compilers.opam\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:opam/package@2.2.0\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"opam compiler :: parsing\", function()\n    it(\"should parse package\", function()\n        assert.same(\n            Result.success {\n                package = \"package\",\n                version = \"2.2.0\",\n            },\n            opam.parse({}, purl())\n        )\n    end)\nend)\n\ndescribe(\"opam compiler :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install opam packages\", function()\n        local ctx = test_helpers.create_context()\n        local manager = require \"mason-core.installer.managers.opam\"\n        stub(manager, \"install\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return opam.install(ctx, {\n                package = \"package\",\n                version = \"1.5.0\",\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(manager.install).was_called(1)\n        assert.spy(manager.install).was_called_with(\"package\", \"1.5.0\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/openvsx_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal common = require \"mason-core.installer.managers.common\"\nlocal match = require \"luassert.match\"\nlocal openvsx = require \"mason-core.installer.compiler.compilers.openvsx\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:openvsx/namespace/name@1.10.1\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"openvsx provider :: download :: parsing\", function()\n    it(\"should parse download source\", function()\n        assert.same(\n            Result.success {\n                download = {\n                    file = \"file-1.10.1.jar\",\n                },\n                downloads = {\n                    {\n                        out_file = \"file-1.10.1.jar\",\n                        download_url = \"https://open-vsx.org/api/namespace/name/1.10.1/file/file-1.10.1.jar\",\n                    },\n                },\n            },\n            openvsx.parse({\n                download = {\n                    file = \"file-{{version}}.jar\",\n                },\n            }, purl())\n        )\n    end)\n\n    it(\"should parse download source with multiple targets\", function()\n        assert.same(\n            Result.success {\n                download = {\n                    target = \"linux_x64\",\n                    file = \"file-linux-amd64-1.0.0.vsix\",\n                },\n                downloads = {\n                    {\n                        out_file = \"file-linux-amd64-1.0.0.vsix\",\n                        download_url = \"https://open-vsx.org/api/namespace/name/1.0.0/file/file-linux-amd64-1.0.0.vsix\",\n                    },\n                },\n            },\n            openvsx.parse({\n                download = {\n                    {\n                        target = \"win_arm\",\n                        file = \"file-win-arm-{{version}}.vsix\",\n                    },\n                    {\n                        target = \"linux_x64\",\n                        file = \"file-linux-amd64-{{version}}.vsix\",\n                    },\n                },\n            }, purl { version = \"1.0.0\" }, { target = \"linux_x64\" })\n        )\n    end)\n\n    it(\"should parse download source with output to different directory\", function()\n        assert.same(\n            Result.success {\n                download = {\n                    file = \"out-dir/file-linux-amd64-1.10.1.vsix\",\n                },\n                downloads = {\n                    {\n                        out_file = \"out-dir/file-linux-amd64-1.10.1.vsix\",\n                        download_url = \"https://open-vsx.org/api/namespace/name/1.10.1/file/file-linux-amd64-1.10.1.vsix\",\n                    },\n                },\n            },\n            openvsx.parse({\n                download = {\n                    file = \"file-linux-amd64-{{version}}.vsix:out-dir/\",\n                },\n            }, purl(), { target = \"linux_x64\" })\n        )\n    end)\n\n    it(\"should recognize target_platform when available\", function()\n        assert.same(\n            Result.success {\n                download = {\n                    file = \"file-linux-1.10.1@win32-arm64.vsix\",\n                    target = \"win_arm64\",\n                    target_platform = \"win32-arm64\",\n                },\n                downloads = {\n                    {\n                        out_file = \"file-linux-1.10.1@win32-arm64.vsix\",\n                        download_url = \"https://open-vsx.org/api/namespace/name/win32-arm64/1.10.1/file/file-linux-1.10.1@win32-arm64.vsix\",\n                    },\n                },\n            },\n            openvsx.parse({\n                download = {\n                    {\n                        target = \"win_arm64\",\n                        file = \"file-linux-{{version}}@win32-arm64.vsix\",\n                        target_platform = \"win32-arm64\",\n                    },\n                },\n            }, purl(), { target = \"win_arm64\" })\n        )\n    end)\nend)\n\ndescribe(\"openvsx provider :: download :: installing\", function()\n    it(\"should install openvsx assets\", function()\n        local ctx = test_helpers.create_context()\n        stub(common, \"download_files\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return openvsx.install(ctx, {\n                download = {\n                    file = \"file-1.10.1.jar\",\n                },\n                downloads = {\n                    {\n                        out_file = \"file-1.10.1.jar\",\n                        download_url = \"https://open-vsx.org/api/namespace/name/1.10.1/file/file-1.10.1.jar\",\n                    },\n                },\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(common.download_files).was_called(1)\n        assert.spy(common.download_files).was_called_with(match.is_ref(ctx), {\n            {\n                out_file = \"file-1.10.1.jar\",\n                download_url = \"https://open-vsx.org/api/namespace/name/1.10.1/file/file-1.10.1.jar\",\n            },\n        })\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/compilers/pypi_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal pypi = require \"mason-core.installer.compiler.compilers.pypi\"\nlocal settings = require \"mason.settings\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param overrides Purl\nlocal function purl(overrides)\n    local purl = Purl.parse(\"pkg:pypi/package@5.5.0\"):get_or_throw()\n    if not overrides then\n        return purl\n    end\n    return vim.tbl_deep_extend(\"force\", purl, overrides)\nend\n\ndescribe(\"pypi compiler :: parsing\", function()\n    it(\"should parse package\", function()\n        settings.set {\n            pip = {\n                install_args = { \"--proxy\", \"http://localghost\" },\n                upgrade_pip = true,\n            },\n        }\n\n        assert.same(\n            Result.success {\n                package = \"package\",\n                version = \"5.5.0\",\n                extra_packages = { \"extra\" },\n                pip = {\n                    upgrade = true,\n                    extra_args = { \"--proxy\", \"http://localghost\" },\n                },\n            },\n            pypi.parse({ extra_packages = { \"extra\" } }, purl())\n        )\n        settings.set(settings._DEFAULT_SETTINGS)\n    end)\nend)\n\ndescribe(\"pypi compiler :: installing\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install pypi packages\", function()\n        local ctx = test_helpers.create_context()\n        local manager = require \"mason-core.installer.managers.pypi\"\n        stub(manager, \"init\", mockx.returns(Result.success()))\n        stub(manager, \"install\", mockx.returns(Result.success()))\n        settings.set {\n            pip = {\n                install_args = { \"--proxy\", \"http://localghost\" },\n                upgrade_pip = true,\n            },\n        }\n\n        local result = ctx:execute(function()\n            return pypi.install(ctx, {\n                package = \"package\",\n                extra = \"lsp\",\n                version = \"1.5.0\",\n                extra_packages = { \"extra\" },\n                pip = {\n                    upgrade = true,\n                    extra_args = { \"--proxy\", \"http://localghost\" },\n                },\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(manager.init).was_called(1)\n        assert.spy(manager.init).was_called_with {\n            package = { name = \"package\", version = \"1.5.0\" },\n            upgrade_pip = true,\n            install_extra_args = { \"--proxy\", \"http://localghost\" },\n        }\n        assert.spy(manager.install).was_called(1)\n        assert.spy(manager.install).was_called_with(\n            \"package\",\n            \"1.5.0\",\n            { extra = \"lsp\", extra_packages = { \"extra\" }, install_extra_args = { \"--proxy\", \"http://localghost\" } }\n        )\n        settings.set(settings._DEFAULT_SETTINGS)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/expr_spec.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal expr = require \"mason-core.installer.compiler.expr\"\nlocal match = require \"luassert.match\"\n\ndescribe(\"registry expressions\", function()\n    it(\"should eval simple expressions\", function()\n        assert.same(Result.success \"Hello, world!\", expr.interpolate(\"Hello, world!\", {}))\n\n        assert.same(\n            Result.success \"Hello, John Doe!\",\n            expr.interpolate(\"Hello, {{firstname}} {{ lastname   }}!\", {\n                firstname = \"John\",\n                lastname = \"Doe\",\n            })\n        )\n    end)\n\n    it(\"should eval nested access\", function()\n        assert.same(\n            Result.success \"Hello, world!\",\n            expr.interpolate(\"Hello, {{greeting.name}}!\", { greeting = { name = \"world\" } })\n        )\n    end)\n\n    it(\"should eval benign expressions\", function()\n        assert.same(\n            Result.success \"Hello, JOHNDOE JR.!\",\n            expr.interpolate(\"Hello, {{greeting.firstname .. greeting.lastname .. tostring(tbl) | to_upper}}!\", {\n                greeting = { firstname = \"John\", lastname = \"Doe\" },\n                tostring = tostring,\n                tbl = setmetatable({}, {\n                    __tostring = function()\n                        return \" Jr.\"\n                    end,\n                }),\n            })\n        )\n\n        assert.same(\n            Result.success \"Gloves\",\n            expr.interpolate(\"G{{ 'Cloves' | strip_prefix(trim) }}\", {\n                trim = \"C\",\n            })\n        )\n    end)\n\n    it(\"should eval expressions with filters\", function()\n        assert.same(\n            Result.success \"Hello, MR. John!\",\n            expr.interpolate(\"Hello, {{prefix|to_upper}} {{  name | trim   }}!\", {\n                prefix = \"Mr.\",\n                trim = _.trim,\n                name = \" John   \",\n            })\n        )\n\n        assert.same(\n            Result.success \"Hello, Sir MR. John!\",\n            expr.interpolate(\"Hello, {{prefix|to_upper | format 'Sir %s'}} {{  name | trim   }}!\", {\n                format = _.format,\n                trim = _.trim,\n                prefix = \"Mr.\",\n                name = \" John   \",\n            })\n        )\n    end)\n\n    it(\"should not interpolate nil values\", function()\n        assert.same(Result.success \"Hello, \", expr.interpolate(\"Hello, {{non_existent}}\", {}))\n        assert.same(Result.success \"\", expr.interpolate(\"{{non_existent}}\", {}))\n    end)\n\n    it(\"should error if piping nil values to functions that require non-nil values\", function()\n        local err = assert.has_error(function()\n            expr.interpolate(\"Hello, {{ non_existent | to_upper }}\", {}):get_or_throw()\n        end)\n        assert.is_true(match.matches \"attempt to index local 'str' %(a nil value%)$\"(err))\n    end)\n\n    it(\"should reject invalid filters\", function()\n        assert.is_true(\n            match.matches [[^.*Invalid filter expression: \"whut\"]](\n                expr.interpolate(\"Hello, {{ value | whut }}\", { value = \"value\" }):err_or_nil()\n            )\n        )\n        assert.is_true(\n            match.matches [[^.*Failed to parse expression: \"wh%-!uut\"]](\n                expr.interpolate(\"Hello, {{ value | wh-!uut }}\", { value = \"value\" }):err_or_nil()\n            )\n        )\n    end)\nend)\n\ndescribe(\"expr filters :: equals/not_equals\", function()\n    it(\"should equals\", function()\n        assert.same(\n            Result.success \"true\",\n            expr.interpolate(\"{{equals('Hello, world!', value)}}\", {\n                value = \"Hello, world!\",\n            })\n        )\n\n        assert.same(\n            Result.success \"true\",\n            expr.interpolate(\"{{ value | equals('Hello, world!') }}\", {\n                value = \"Hello, world!\",\n            })\n        )\n\n        assert.same(\n            Result.success \"false\",\n            expr.interpolate(\"{{ value | equals('Hello, John!') }}\", {\n                value = \"Hello, world!\",\n            })\n        )\n    end)\n\n    it(\"should not equals\", function()\n        assert.same(\n            Result.success \"true\",\n            expr.interpolate(\"{{not_equals('Hello, John!', value)}}\", {\n                value = \"Hello, world!\",\n            })\n        )\n\n        assert.same(\n            Result.success \"true\",\n            expr.interpolate(\"{{ value | not_equals('Hello, John!') }}\", {\n                value = \"Hello, world!\",\n            })\n        )\n\n        assert.same(\n            Result.success \"false\",\n            expr.interpolate(\"{{ value | not_equals('Hello, world!') }}\", {\n                value = \"Hello, world!\",\n            })\n        )\n    end)\nend)\n\ndescribe(\"expr filters :: take_if{_not}\", function()\n    it(\"should take if value matches\", function()\n        assert.same(\n            Result.success \"Hello, world!\",\n            expr.interpolate(\"Hello, {{ take_if(equals('world!'), value) }}\", {\n                value = \"world!\",\n            })\n        )\n\n        assert.same(\n            Result.success \"Hello, world!\",\n            expr.interpolate(\"Hello, {{ value | take_if(equals('world!')) }}\", {\n                value = \"world!\",\n            })\n        )\n\n        assert.same(\n            Result.success \"\",\n            expr.interpolate(\"{{ take_if(equals('Hello John!'), greeting) }}\", {\n                greeting = \"Hello World!\",\n            })\n        )\n\n        assert.same(\n            Result.success \"\",\n            expr.interpolate(\"{{ take_if(false, greeting) }}\", {\n                greeting = \"Hello World!\",\n            })\n        )\n\n        assert.same(\n            Result.success \"Hello World!\",\n            expr.interpolate(\"{{ take_if(true, greeting) }}\", {\n                greeting = \"Hello World!\",\n            })\n        )\n    end)\n\n    it(\"should not take if value matches\", function()\n        assert.same(\n            Result.success \"Hello, world!\",\n            expr.interpolate(\"Hello, {{ take_if_not(equals('John!'), value) }}\", {\n                value = \"world!\",\n            })\n        )\n\n        assert.same(\n            Result.success \"Hello, world!\",\n            expr.interpolate(\"Hello, {{ value | take_if_not(equals('john!')) }}\", {\n                value = \"world!\",\n            })\n        )\n\n        assert.same(\n            Result.success \"\",\n            expr.interpolate(\"{{ take_if_not(equals('Hello World!'), greeting) }}\", {\n                greeting = \"Hello World!\",\n            })\n        )\n\n        assert.same(\n            Result.success \"Hello World!\",\n            expr.interpolate(\"{{ take_if_not(false, greeting) }}\", {\n                greeting = \"Hello World!\",\n            })\n        )\n\n        assert.same(\n            Result.success \"\",\n            expr.interpolate(\"{{ take_if_not(true, greeting) }}\", {\n                greeting = \"Hello World!\",\n            })\n        )\n    end)\nend)\n\ndescribe(\"expr filters :: strip_{suffix,prefix}\", function()\n    it(\"should strip prefix\", function()\n        assert.same(\n            Result.success \"1.0.0\",\n            expr.interpolate([[{{value | strip_prefix(\"v\") }}]], {\n                value = \"v1.0.0\",\n            })\n        )\n    end)\n\n    it(\"should strip suffix\", function()\n        assert.same(\n            Result.success \"bin/file\",\n            expr.interpolate([[{{value | strip_suffix(\".tar.gz\") }}]], {\n                value = \"bin/file.tar.gz\",\n            })\n        )\n    end)\nend)\n\ndescribe(\"table interpolation\", function()\n    it(\"should interpolate nested values\", function()\n        assert.same(\n            Result.success {\n                some = {\n                    nested = {\n                        value = \"here\",\n                    },\n                },\n            },\n            expr.tbl_interpolate({\n                some = {\n                    nested = {\n                        value = \"{{value}}\",\n                    },\n                },\n            }, { value = \"here\" })\n        )\n    end)\n\n    it(\"should only only interpolate string values\", function()\n        assert.same(\n            Result.success {\n                a = 1,\n                b = { c = 2 },\n                d = \"Hello!\",\n            },\n            expr.tbl_interpolate({\n                a = 1,\n                b = { c = 2 },\n                d = \"Hello!\",\n            }, {})\n        )\n    end)\n\n    it(\"should interpolate string keys\", function()\n        assert.same(\n            Result.success {\n                [\"a-1.2.3\"] = \"1.2.3\",\n                [12] = \"12\",\n            },\n            expr.tbl_interpolate({\n                [\"a-{{version}}\"] = \"{{version}}\",\n                [12] = \"12\",\n            }, { version = \"1.2.3\" })\n        )\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/link_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\nlocal fs = require \"mason-core.fs\"\nlocal link = require \"mason-core.installer.compiler.link\"\nlocal match = require \"luassert.match\"\nlocal path = require \"mason-core.path\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"registry linker\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should expand bin table\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"file_exists\")\n        stub(ctx.fs, \"chmod\")\n        stub(ctx.fs, \"fstat\")\n\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), \"exec.sh\").returns(true)\n        ctx.fs.fstat.on_call_with(match.is_ref(ctx.fs), \"exec.sh\").returns {\n            mode = 493, -- 0755\n        }\n\n        local result = link.bin(\n            ctx,\n            {\n                bin = {\n                    [\"exec\"] = \"exec.sh\",\n                },\n            },\n            Purl.parse(\"pkg:dummy/package@v1.0.0\"):get_or_throw(),\n            {\n                metadata = \"value\",\n            }\n        )\n\n        assert.same(\n            Result.success {\n                [\"exec\"] = \"exec.sh\",\n            },\n            result\n        )\n        assert.same({\n            [\"exec\"] = \"exec.sh\",\n        }, ctx.links.bin)\n\n        assert.spy(ctx.fs.chmod).was_not_called()\n    end)\n\n    it(\"should chmod executable if necessary\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"file_exists\")\n        stub(ctx.fs, \"chmod\")\n        stub(ctx.fs, \"fstat\")\n\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), \"exec.sh\").returns(true)\n        ctx.fs.fstat.on_call_with(match.is_ref(ctx.fs), \"exec.sh\").returns {\n            mode = 420, -- 0644\n        }\n\n        local result = link.bin(\n            ctx,\n            {\n                bin = {\n                    [\"exec\"] = \"exec.sh\",\n                },\n            },\n            Purl.parse(\"pkg:dummy/package@v1.0.0\"):get_or_throw(),\n            {\n                metadata = \"value\",\n            }\n        )\n\n        assert.is_true(result:is_success())\n        assert.spy(ctx.fs.chmod).was_called(1)\n        assert.spy(ctx.fs.chmod).was_called_with(match.is_ref(ctx.fs), \"exec.sh\", 493)\n    end)\n\n    it(\"should interpolate bin table\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"file_exists\")\n        stub(ctx.fs, \"chmod\")\n        stub(ctx.fs, \"fstat\")\n\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), \"v1.0.0-exec.sh\").returns(true)\n        ctx.fs.fstat.on_call_with(match.is_ref(ctx.fs), \"v1.0.0-exec.sh\").returns {\n            mode = 493, -- 0755\n        }\n\n        local result = link.bin(\n            ctx,\n            {\n                bin = {\n                    [\"exec\"] = \"{{version}}-{{source.script}}\",\n                },\n            },\n            Purl.parse(\"pkg:dummy/package@v1.0.0\"):get_or_throw(),\n            {\n                script = \"exec.sh\",\n            }\n        )\n\n        assert.same(\n            Result.success {\n                [\"exec\"] = \"v1.0.0-exec.sh\",\n            },\n            result\n        )\n    end)\n\n    it(\"should delegate bin paths\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"file_exists\")\n        stub(ctx.fs, \"chmod\")\n        stub(ctx.fs, \"fstat\")\n\n        local matrix = {\n            [\"cargo:executable\"] = \"bin/executable\",\n            [\"composer:executable\"] = \"vendor/bin/executable\",\n            [\"golang:executable\"] = \"executable\",\n            [\"luarocks:executable\"] = \"bin/executable\",\n            [\"npm:executable\"] = \"node_modules/.bin/executable\",\n            [\"nuget:executable\"] = \"executable\",\n            [\"opam:executable\"] = \"bin/executable\",\n            -- [\"pypi:executable\"] = \"venv/bin/executable\",\n        }\n\n        for bin, path in pairs(matrix) do\n            ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), path).returns(true)\n            ctx.fs.fstat.on_call_with(match.is_ref(ctx.fs), path).returns {\n                mode = 493, -- 0755\n            }\n\n            local result = link.bin(ctx, {\n                bin = {\n                    [\"executable\"] = bin,\n                },\n            }, Purl.parse(\"pkg:dummy/package@v1.0.0\"):get_or_throw(), {})\n\n            assert.same(\n                Result.success {\n                    [\"executable\"] = path,\n                },\n                result\n            )\n        end\n    end)\n\n    it(\"should register share links\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"file_exists\")\n        stub(fs.sync, \"file_exists\")\n        stub(vim.fn, \"glob\")\n\n        vim.fn.glob.on_call_with(path.concat { ctx.cwd:get(), \"v1.0.0/dir/\" } .. \"**/*\", false, true).returns {\n            path.concat { ctx.cwd:get(), \"v1.0.0\", \"dir\", \"file1\" },\n            path.concat { ctx.cwd:get(), \"v1.0.0\", \"dir\", \"file2\" },\n            path.concat { ctx.cwd:get(), \"v1.0.0\", \"dir\", \"file3\" },\n        }\n        fs.sync.file_exists.on_call_with(path.concat { ctx.cwd:get(), \"v1.0.0\", \"dir\", \"file1\" }).returns(true)\n        fs.sync.file_exists.on_call_with(path.concat { ctx.cwd:get(), \"v1.0.0\", \"dir\", \"file2\" }).returns(true)\n        fs.sync.file_exists.on_call_with(path.concat { ctx.cwd:get(), \"v1.0.0\", \"dir\", \"file3\" }).returns(true)\n\n        local result = link.share(\n            ctx,\n            {\n                share = {\n                    [\"file\"] = \"{{version}}-{{source.file}}\",\n                    [\"dir/\"] = \"{{version}}/dir/\",\n                    [\"empty/\"] = \"{{source.empty}}\",\n                },\n            },\n            Purl.parse(\"pkg:dummy/package@v1.0.0\"):get_or_throw(),\n            {\n                file = \"file\",\n            }\n        )\n\n        assert.same(\n            Result.success {\n                [\"file\"] = \"v1.0.0-file\",\n                [\"dir/file1\"] = \"v1.0.0/dir/file1\",\n                [\"dir/file2\"] = \"v1.0.0/dir/file2\",\n                [\"dir/file3\"] = \"v1.0.0/dir/file3\",\n            },\n            result\n        )\n\n        assert.same({\n            [\"file\"] = \"v1.0.0-file\",\n            [\"dir/file1\"] = \"v1.0.0/dir/file1\",\n            [\"dir/file2\"] = \"v1.0.0/dir/file2\",\n            [\"dir/file3\"] = \"v1.0.0/dir/file3\",\n        }, ctx.links.share)\n    end)\n\n    it(\"should register opt links\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"file_exists\")\n        stub(fs.sync, \"file_exists\")\n        stub(vim.fn, \"glob\")\n\n        vim.fn.glob.on_call_with(path.concat { ctx.cwd:get(), \"v1.0.0/dir/\" } .. \"**/*\", false, true).returns {\n            path.concat { ctx.cwd:get(), \"v1.0.0\", \"dir\", \"file1\" },\n            path.concat { ctx.cwd:get(), \"v1.0.0\", \"dir\", \"file2\" },\n            path.concat { ctx.cwd:get(), \"v1.0.0\", \"dir\", \"file3\" },\n        }\n        fs.sync.file_exists.on_call_with(path.concat { ctx.cwd:get(), \"v1.0.0\", \"dir\", \"file1\" }).returns(true)\n        fs.sync.file_exists.on_call_with(path.concat { ctx.cwd:get(), \"v1.0.0\", \"dir\", \"file2\" }).returns(true)\n        fs.sync.file_exists.on_call_with(path.concat { ctx.cwd:get(), \"v1.0.0\", \"dir\", \"file3\" }).returns(true)\n\n        local result = link.opt(\n            ctx,\n            {\n                opt = {\n                    [\"file\"] = \"{{version}}-{{source.file}}\",\n                    [\"dir/\"] = \"{{version}}/dir/\",\n                    [\"empty/\"] = \"{{source.empty}}\",\n                },\n            },\n            Purl.parse(\"pkg:dummy/package@v1.0.0\"):get_or_throw(),\n            {\n                file = \"file\",\n            }\n        )\n\n        assert.same(\n            Result.success {\n                [\"file\"] = \"v1.0.0-file\",\n                [\"dir/file1\"] = \"v1.0.0/dir/file1\",\n                [\"dir/file2\"] = \"v1.0.0/dir/file2\",\n                [\"dir/file3\"] = \"v1.0.0/dir/file3\",\n            },\n            result\n        )\n\n        assert.same({\n            [\"file\"] = \"v1.0.0-file\",\n            [\"dir/file1\"] = \"v1.0.0/dir/file1\",\n            [\"dir/file2\"] = \"v1.0.0/dir/file2\",\n            [\"dir/file3\"] = \"v1.0.0/dir/file3\",\n        }, ctx.links.opt)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/compiler/util_spec.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal match = require \"luassert.match\"\nlocal platform = require \"mason-core.platform\"\nlocal test_helpers = require \"mason-test.helpers\"\nlocal util = require \"mason-core.installer.compiler.util\"\n\ndescribe(\"registry installer util\", function()\n    it(\"should coalesce single target\", function()\n        local source = { value = \"here\" }\n        local coalesced = util.coalesce_by_target(source, {}):get_or_nil()\n        assert.is_true(match.is_ref(source)(coalesced))\n    end)\n\n    it(\"should coalesce multiple targets\", function()\n        local source = { target = \"VIC64\", value = \"here\" }\n        local coalesced = util.coalesce_by_target({\n            {\n                target = \"linux_arm64\",\n                value = \"here\",\n            },\n            source,\n        }, { target = \"VIC64\" }):get_or_nil()\n\n        assert.is_true(match.is_ref(source)(coalesced))\n    end)\n\n    it(\"should accept valid platform\", function()\n        platform.is.VIC64 = true\n        local result = util.ensure_valid_platform {\n            \"VIC64\",\n            \"linux_arm64\",\n        }\n        assert.is_true(result:is_success())\n        platform.is.VIC64 = nil\n    end)\n\n    it(\"should reject invalid platform\", function()\n        local result = util.ensure_valid_platform { \"VIC64\" }\n        assert.same(Result.failure \"PLATFORM_UNSUPPORTED\", result)\n    end)\n\n    it(\"should accept valid version\", function()\n        local ctx = test_helpers.create_context { install_opts = { version = \"1.0.0\" } }\n        local result = ctx:execute(function()\n            return util.ensure_valid_version(function()\n                return Result.success { \"1.0.0\", \"2.0.0\", \"3.0.0\" }\n            end)\n        end)\n        assert.is_true(result:is_success())\n    end)\n\n    it(\"should reject invalid version\", function()\n        local ctx = test_helpers.create_context { install_opts = { version = \"13.3.7\" } }\n        local result = ctx:execute(function()\n            return util.ensure_valid_version(function()\n                return Result.success { \"1.0.0\", \"2.0.0\", \"3.0.0\" }\n            end)\n        end)\n        assert.same(Result.failure [[Version \"13.3.7\" is not available.]], result)\n    end)\n\n    it(\"should gracefully accept version if unable to resolve available versions\", function()\n        local ctx = test_helpers.create_context { install_opts = { version = \"13.3.7\" } }\n        local result = ctx:execute(function()\n            return util.ensure_valid_version(function()\n                return Result.failure()\n            end)\n        end)\n        assert.is_true(result:is_success())\n    end)\n\n    it(\"should accept version if in force mode\", function()\n        local ctx = test_helpers.create_context { install_opts = { version = \"13.3.7\", force = true } }\n        local result = ctx:execute(function()\n            return util.ensure_valid_version(function()\n                return Result.success { \"1.0.0\" }\n            end)\n        end)\n        assert.is_true(result:is_success())\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/context_spec.lua",
    "content": "local a = require \"mason-core.async\"\nlocal match = require \"luassert.match\"\nlocal path = require \"mason-core.path\"\nlocal pypi = require \"mason-core.installer.managers.pypi\"\nlocal registry = require \"mason-registry\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"installer\", function()\n    ---@module \"mason-core.platform\"\n    local platform\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    before_each(function()\n        package.loaded[\"mason-core.installer.platform\"] = nil\n        package.loaded[\"mason-core.installer.context\"] = nil\n        platform = require \"mason-core.platform\"\n    end)\n\n    it(\"should write shell exec wrapper on Unix\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"write_file\")\n        stub(ctx.fs, \"file_exists\")\n        stub(ctx.fs, \"dir_exists\")\n        stub(ctx.fs, \"chmod_exec\")\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), \"my-executable\").returns(false)\n        ctx.fs.dir_exists.on_call_with(match.is_ref(ctx.fs), \"my-executable\").returns(false)\n\n        ctx:write_shell_exec_wrapper(\"my-executable\", \"bash -c 'echo $GREETING'\", {\n            GREETING = \"Hello World!\",\n        })\n\n        assert.spy(ctx.fs.write_file).was_called(1)\n        assert.spy(ctx.fs.write_file).was_called_with(\n            match.is_ref(ctx.fs),\n            \"my-executable\",\n            [[#!/usr/bin/env bash\nexport GREETING=\"Hello World!\"\nexec bash -c 'echo $GREETING' \"$@\"]]\n        )\n    end)\n\n    it(\"should write shell exec wrapper on Windows\", function()\n        platform.is.darwin = false\n        platform.is.mac = false\n        platform.is.unix = false\n        platform.is.linux = false\n        platform.is.win = true\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"write_file\")\n        stub(ctx.fs, \"file_exists\")\n        stub(ctx.fs, \"dir_exists\")\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), \"my-executable\").returns(false)\n        ctx.fs.dir_exists.on_call_with(match.is_ref(ctx.fs), \"my-executable\").returns(false)\n\n        ctx:write_shell_exec_wrapper(\"my-executable\", \"cmd.exe /C echo %GREETING%\", {\n            GREETING = \"Hello World!\",\n        })\n\n        assert.spy(ctx.fs.write_file).was_called(1)\n        assert.spy(ctx.fs.write_file).was_called_with(\n            match.is_ref(ctx.fs),\n            \"my-executable.cmd\",\n            [[@ECHO off\nSET GREETING=Hello World!\ncmd.exe /C echo %GREETING% %*]]\n        )\n    end)\n\n    it(\"should not write shell exec wrapper if new executable path already exists\", function()\n        local exec_rel_path = path.concat { \"obscure\", \"path\", \"to\", \"server\" }\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"file_exists\")\n        stub(ctx.fs, \"dir_exists\")\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), exec_rel_path).returns(true)\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), \"my-wrapper-script\").returns(true)\n        ctx.fs.dir_exists.on_call_with(match.is_ref(ctx.fs), \"my-wrapper-script\").returns(true)\n\n        local err = assert.has_error(function()\n            ctx:write_shell_exec_wrapper(\"my-wrapper-script\", \"contents\")\n        end)\n\n        assert.equals([[Cannot write exec wrapper to \"my-wrapper-script\" because the file already exists.]], err)\n    end)\n\n    it(\"should write Node exec wrapper\", function()\n        local js_rel_path = path.concat { \"some\", \"obscure\", \"path\", \"server.js\" }\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"write_shell_exec_wrapper\")\n        stub(ctx.fs, \"file_exists\")\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), js_rel_path).returns(true)\n\n        ctx:write_node_exec_wrapper(\"my-wrapper-script\", js_rel_path)\n\n        assert.spy(ctx.write_shell_exec_wrapper).was_called(1)\n        assert.spy(ctx.write_shell_exec_wrapper).was_called_with(\n            match.is_ref(ctx),\n            \"my-wrapper-script\",\n            (\"node %q\"):format(path.concat { ctx:get_install_path(), js_rel_path })\n        )\n    end)\n\n    it(\"should write Ruby exec wrapper\", function()\n        local js_rel_path = path.concat { \"some\", \"obscure\", \"path\", \"server.js\" }\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"write_shell_exec_wrapper\")\n        stub(ctx.fs, \"file_exists\")\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), js_rel_path).returns(true)\n\n        ctx:write_ruby_exec_wrapper(\"my-wrapper-script\", js_rel_path)\n\n        assert.spy(ctx.write_shell_exec_wrapper).was_called(1)\n        assert.spy(ctx.write_shell_exec_wrapper).was_called_with(\n            match.is_ref(ctx),\n            \"my-wrapper-script\",\n            (\"ruby %q\"):format(path.concat { ctx:get_install_path(), js_rel_path })\n        )\n    end)\n\n    it(\"should not write Node exec wrapper if the target script doesn't exist\", function()\n        local js_rel_path = path.concat { \"some\", \"obscure\", \"path\", \"server.js\" }\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"write_shell_exec_wrapper\")\n        stub(ctx.fs, \"file_exists\")\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), js_rel_path).returns(false)\n\n        local err = assert.has_error(function()\n            ctx:write_node_exec_wrapper(\"my-wrapper-script\", js_rel_path)\n        end)\n\n        assert.equals(\n            [[Cannot write Node exec wrapper for path \"some/obscure/path/server.js\" as it doesn't exist.]],\n            err\n        )\n        assert.spy(ctx.write_shell_exec_wrapper).was_called(0)\n    end)\n\n    it(\"should write Python exec wrapper\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.cwd, \"get\")\n        ctx.cwd.get.returns \"/tmp/placeholder\"\n        stub(ctx, \"write_shell_exec_wrapper\")\n\n        ctx:write_pyvenv_exec_wrapper(\"my-wrapper-script\", \"my-module\")\n\n        assert.spy(ctx.write_shell_exec_wrapper).was_called(1)\n        assert.spy(ctx.write_shell_exec_wrapper).was_called_with(\n            match.is_ref(ctx),\n            \"my-wrapper-script\",\n            (\"%q -m my-module\"):format(path.concat { pypi.venv_path(ctx:get_install_path()), \"python\" })\n        )\n    end)\n\n    it(\"should not write Python exec wrapper if module cannot be found\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.cwd, \"get\")\n        ctx.cwd.get.returns \"/tmp/placeholder\"\n        stub(ctx, \"write_shell_exec_wrapper\")\n        stub(ctx.spawn, \"python\")\n\n        ctx.spawn.python.invokes(function()\n            error \"\"\n        end)\n\n        local err = assert.has_error(function()\n            ctx:write_pyvenv_exec_wrapper(\"my-wrapper-script\", \"my-module\")\n        end)\n\n        assert.equals([[Cannot write Python exec wrapper for module \"my-module\" as it doesn't exist.]], err)\n        assert.spy(ctx.write_shell_exec_wrapper).was_called(0)\n    end)\n\n    it(\"should write exec wrapper\", function()\n        local exec_rel_path = path.concat { \"obscure\", \"path\", \"to\", \"server\" }\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"write_shell_exec_wrapper\")\n        stub(ctx.fs, \"file_exists\")\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), exec_rel_path).returns(true)\n\n        ctx:write_exec_wrapper(\"my-wrapper-script\", exec_rel_path)\n\n        assert.spy(ctx.write_shell_exec_wrapper).was_called(1)\n        assert\n            .spy(ctx.write_shell_exec_wrapper)\n            .was_called_with(\n                match.is_ref(ctx),\n                \"my-wrapper-script\",\n                (\"%q\"):format(path.concat { ctx:get_install_path(), exec_rel_path })\n            )\n    end)\n\n    it(\"should not write exec wrapper if target executable doesn't exist\", function()\n        local exec_rel_path = path.concat { \"obscure\", \"path\", \"to\", \"server\" }\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"write_shell_exec_wrapper\")\n        stub(ctx.fs, \"file_exists\")\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), exec_rel_path).returns(false)\n\n        local err = assert.has_error(function()\n            ctx:write_exec_wrapper(\"my-wrapper-script\", exec_rel_path)\n        end)\n\n        assert.equals([[Cannot write exec wrapper for path \"obscure/path/to/server\" as it doesn't exist.]], err)\n        assert.spy(ctx.write_shell_exec_wrapper).was_called(0)\n    end)\n\n    it(\"should write PHP exec wrapper\", function()\n        local php_rel_path = path.concat { \"some\", \"obscure\", \"path\", \"cli.php\" }\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"write_shell_exec_wrapper\")\n        stub(ctx.fs, \"file_exists\")\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), php_rel_path).returns(true)\n\n        ctx:write_php_exec_wrapper(\"my-wrapper-script\", php_rel_path)\n\n        assert.spy(ctx.write_shell_exec_wrapper).was_called(1)\n        assert.spy(ctx.write_shell_exec_wrapper).was_called_with(\n            match.is_ref(ctx),\n            \"my-wrapper-script\",\n            (\"php %q\"):format(path.concat { ctx:get_install_path(), php_rel_path })\n        )\n    end)\n\n    it(\"should not write PHP exec wrapper if the target script doesn't exist\", function()\n        local php_rel_path = path.concat { \"some\", \"obscure\", \"path\", \"cli.php\" }\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"write_shell_exec_wrapper\")\n        stub(ctx.fs, \"file_exists\")\n        ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), php_rel_path).returns(false)\n\n        local err = assert.has_error(function()\n            ctx:write_php_exec_wrapper(\"my-wrapper-script\", php_rel_path)\n        end)\n\n        assert.equals([[Cannot write PHP exec wrapper for path \"some/obscure/path/cli.php\" as it doesn't exist.]], err)\n        assert.spy(ctx.write_shell_exec_wrapper).was_called(0)\n    end)\n\n    it(\"should await callback-style async function\", function()\n        local value = a.run_blocking(function()\n            local ctx = test_helpers.create_context()\n            return ctx:execute(function()\n                return ctx:await(function(resolve, reject)\n                    vim.defer_fn(function()\n                        resolve \"Value!\"\n                    end, 500)\n                end)\n            end)\n        end)\n\n        assert.equals(\"Value!\", value)\n    end)\n\n    it(\"should propagate errors in callback-style async function\", function()\n        local guard = spy.new()\n        local error = assert.has_error(function()\n            a.run_blocking(function()\n                local ctx = test_helpers.create_context()\n                return ctx:execute(function()\n                    ctx:await(function(resolve, reject)\n                        vim.defer_fn(function()\n                            reject \"Error!\"\n                        end, 500)\n                    end)\n                    guard()\n                end)\n            end)\n        end)\n\n        assert.equals(\"Error!\", error)\n        assert.spy(guard).was_called(0)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/linker_spec.lua",
    "content": "local a = require \"mason-core.async\"\nlocal fs = require \"mason-core.fs\"\nlocal path = require \"mason-core.path\"\nlocal registry = require \"mason-registry\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\nlocal WIN_CMD_SCRIPT = [[@ECHO off\nGOTO start\n:find_dp0\nSET dp0=%%~dp0\nEXIT /b\n:start\nSETLOCAL\nCALL :find_dp0\n\nendLocal & goto #_undefined_# 2>NUL || title %%COMSPEC%% & \"%%dp0%%\\%s\" %%*]]\n\ndescribe(\"linker\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    ---@module \"mason-core.installer.linker\"\n    local linker\n    ---@module \"mason-core.platform\"\n    local platform\n\n    before_each(function()\n        package.loaded[\"mason-core.platform\"] = nil\n        package.loaded[\"mason-core.installer.linker\"] = nil\n        platform = require \"mason-core.platform\"\n        linker = require \"mason-core.installer.linker\"\n    end)\n\n    it(\"should symlink executable on Unix\", function()\n        local ctx = test_helpers.create_context()\n\n        stub(fs.async, \"file_exists\")\n        stub(fs.async, \"symlink\")\n        stub(fs.async, \"write_file\")\n\n        fs.async.file_exists.on_call_with(ctx.location:bin \"my-executable\").returns(false)\n        fs.async.file_exists.on_call_with(ctx.location:bin \"another-executable\").returns(false)\n        fs.async.file_exists\n            .on_call_with(path.concat { ctx:get_install_path(), \"nested\", \"path\", \"my-executable\" })\n            .returns(true)\n        fs.async.file_exists.on_call_with(path.concat { ctx:get_install_path(), \"another-executable\" }).returns(true)\n\n        ctx:link_bin(\"my-executable\", path.concat { \"nested\", \"path\", \"my-executable\" })\n        ctx:link_bin(\"another-executable\", \"another-executable\")\n        local result = a.run_blocking(linker.link, ctx)\n        assert.is_true(result:is_success())\n\n        assert.spy(fs.async.write_file).was_called(0)\n        assert.spy(fs.async.symlink).was_called(2)\n        assert\n            .spy(fs.async.symlink)\n            .was_called_with(\"../packages/dummy/another-executable\", ctx.location:bin \"another-executable\")\n        assert\n            .spy(fs.async.symlink)\n            .was_called_with(\"../packages/dummy/nested/path/my-executable\", ctx.location:bin \"my-executable\")\n    end)\n\n    it(\"should write executable wrapper on Windows\", function()\n        local ctx = test_helpers.create_context()\n\n        platform.is.darwin = false\n        platform.is.mac = false\n        platform.is.linux = false\n        platform.is.unix = false\n        platform.is.win = true\n\n        stub(fs.async, \"file_exists\")\n        stub(fs.async, \"symlink\")\n        stub(fs.async, \"write_file\")\n\n        fs.async.file_exists.on_call_with(ctx.location:bin \"my-executable\").returns(false)\n        fs.async.file_exists.on_call_with(ctx.location:bin \"another-executable\").returns(false)\n        fs.async.file_exists\n            .on_call_with(path.concat { ctx:get_install_path(), \"nested\", \"path\", \"my-executable\" })\n            .returns(true)\n        fs.async.file_exists.on_call_with(path.concat { ctx:get_install_path(), \"another-executable\" }).returns(true)\n\n        ctx:link_bin(\"my-executable\", path.concat { \"nested\", \"path\", \"my-executable\" })\n        ctx:link_bin(\"another-executable\", \"another-executable\")\n\n        local result = a.run_blocking(linker.link, ctx)\n        assert.is_true(result:is_success())\n\n        assert.spy(fs.async.symlink).was_called(0)\n        assert.spy(fs.async.write_file).was_called(2)\n        assert\n            .spy(fs.async.write_file)\n            .was_called_with(ctx.location:bin \"another-executable.cmd\", WIN_CMD_SCRIPT:format \"..\\\\packages\\\\dummy\\\\another-executable\")\n        assert\n            .spy(fs.async.write_file)\n            .was_called_with(\n                ctx.location:bin \"my-executable.cmd\",\n                WIN_CMD_SCRIPT:format \"..\\\\packages\\\\dummy\\\\nested\\\\path\\\\my-executable\"\n            )\n    end)\n\n    it(\"should symlink share files\", function()\n        local ctx = test_helpers.create_context()\n\n        stub(fs.async, \"mkdirp\")\n        stub(fs.async, \"dir_exists\")\n        stub(fs.async, \"file_exists\")\n        stub(fs.async, \"symlink\")\n        stub(fs.async, \"write_file\")\n\n        -- mock non-existent dest files\n        fs.async.file_exists.on_call_with(ctx.location:share \"share-file\").returns(false)\n        fs.async.file_exists.on_call_with(ctx.location:share(path.concat { \"nested\", \"share-file\" })).returns(false)\n\n        fs.async.dir_exists.on_call_with(ctx.location:share \"nested/path\").returns(false)\n\n        -- mock existent source files\n        fs.async.file_exists.on_call_with(path.concat { ctx:get_install_path(), \"share-file\" }).returns(true)\n        fs.async.file_exists\n            .on_call_with(path.concat { ctx:get_install_path(), \"nested\", \"path\", \"to\", \"share-file\" })\n            .returns(true)\n\n        ctx.links.share[\"nested/path/share-file\"] = path.concat { \"nested\", \"path\", \"to\", \"share-file\" }\n        ctx.links.share[\"share-file\"] = \"share-file\"\n\n        local result = a.run_blocking(linker.link, ctx)\n\n        assert.is_true(result:is_success())\n\n        assert.spy(fs.async.write_file).was_called(0)\n        assert.spy(fs.async.symlink).was_called(2)\n        assert.spy(fs.async.symlink).was_called_with(\"../packages/dummy/share-file\", ctx.location:share \"share-file\")\n        assert\n            .spy(fs.async.symlink)\n            .was_called_with(\"../../../packages/dummy/nested/path/to/share-file\", ctx.location:share \"nested/path/share-file\")\n\n        assert.spy(fs.async.mkdirp).was_called(2)\n        assert.spy(fs.async.mkdirp).was_called_with(ctx.location:share \"nested/path\")\n    end)\n\n    it(\"should copy share files on Windows\", function()\n        local ctx = test_helpers.create_context()\n\n        platform.is.darwin = false\n        platform.is.mac = false\n        platform.is.linux = false\n        platform.is.unix = false\n        platform.is.win = true\n\n        stub(fs.async, \"mkdirp\")\n        stub(fs.async, \"dir_exists\")\n        stub(fs.async, \"file_exists\")\n        stub(fs.async, \"copy_file\")\n\n        -- mock non-existent dest files\n        fs.async.file_exists.on_call_with(ctx.location:share \"share-file\").returns(false)\n        fs.async.file_exists.on_call_with(ctx.location:share(path.concat { \"nested\", \"share-file\" })).returns(false)\n\n        fs.async.dir_exists.on_call_with(ctx.location:share \"nested/path\").returns(false)\n\n        -- mock existent source files\n        fs.async.file_exists.on_call_with(path.concat { ctx:get_install_path(), \"share-file\" }).returns(true)\n        fs.async.file_exists\n            .on_call_with(path.concat { ctx:get_install_path(), \"nested\", \"path\", \"to\", \"share-file\" })\n            .returns(true)\n\n        ctx.links.share[\"nested/path/share-file\"] = path.concat { \"nested\", \"path\", \"to\", \"share-file\" }\n        ctx.links.share[\"share-file\"] = \"share-file\"\n\n        local result = linker.link(ctx)\n\n        assert.is_true(result:is_success())\n\n        assert.spy(fs.async.copy_file).was_called(2)\n        assert\n            .spy(fs.async.copy_file)\n            .was_called_with(path.concat { ctx:get_install_path(), \"share-file\" }, ctx.location:share \"share-file\", { excl = true })\n        assert.spy(fs.async.copy_file).was_called_with(\n            path.concat { ctx:get_install_path(), \"nested\", \"path\", \"to\", \"share-file\" },\n            ctx.location:share \"nested/path/share-file\",\n            { excl = true }\n        )\n\n        assert.spy(fs.async.mkdirp).was_called(2)\n        assert.spy(fs.async.mkdirp).was_called_with(ctx.location:share \"nested/path\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/managers/cargo_spec.lua",
    "content": "local cargo = require \"mason-core.installer.managers.cargo\"\nlocal match = require \"luassert.match\"\nlocal spy = require \"luassert.spy\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"cargo manager\", function()\n    it(\"should install\", function()\n        local ctx = test_helpers.create_context()\n\n        ctx:execute(function()\n            cargo.install(\"my-crate\", \"1.0.0\")\n        end)\n\n        assert.spy(ctx.spawn.cargo).was_called(1)\n        assert.spy(ctx.spawn.cargo).was_called_with {\n            \"install\",\n            \"--root\",\n            \".\",\n            { \"--version\", \"1.0.0\" },\n            vim.NIL, -- features\n            vim.NIL, -- locked\n            \"my-crate\",\n        }\n    end)\n\n    it(\"should write output\", function()\n        local ctx = test_helpers.create_context()\n        spy.on(ctx.stdio_sink, \"stdout\")\n\n        ctx:execute(function()\n            cargo.install(\"my-crate\", \"1.0.0\")\n        end)\n\n        assert\n            .spy(ctx.stdio_sink.stdout)\n            .was_called_with(match.is_ref(ctx.stdio_sink), \"Installing crate my-crate@1.0.0…\\n\")\n    end)\n\n    it(\"should install locked\", function()\n        local ctx = test_helpers.create_context()\n        ctx:execute(function()\n            cargo.install(\"my-crate\", \"1.0.0\", {\n                locked = true,\n            })\n        end)\n\n        assert.spy(ctx.spawn.cargo).was_called(1)\n        assert.spy(ctx.spawn.cargo).was_called_with {\n            \"install\",\n            \"--root\",\n            \".\",\n            { \"--version\", \"1.0.0\" },\n            vim.NIL, -- features\n            \"--locked\", -- locked\n            \"my-crate\",\n        }\n    end)\n\n    it(\"should install provided features\", function()\n        local ctx = test_helpers.create_context()\n        ctx:execute(function()\n            cargo.install(\"my-crate\", \"1.0.0\", {\n                features = \"lsp,cli\",\n            })\n        end)\n\n        assert.spy(ctx.spawn.cargo).was_called(1)\n        assert.spy(ctx.spawn.cargo).was_called_with {\n            \"install\",\n            \"--root\",\n            \".\",\n            { \"--version\", \"1.0.0\" },\n            { \"--features\", \"lsp,cli\" }, -- features\n            vim.NIL, -- locked\n            \"my-crate\",\n        }\n    end)\n\n    it(\"should install git tag source\", function()\n        local ctx = test_helpers.create_context()\n        ctx:execute(function()\n            cargo.install(\"my-crate\", \"1.0.0\", {\n                git = {\n                    url = \"https://github.com/neovim/neovim\",\n                },\n            })\n        end)\n\n        assert.spy(ctx.spawn.cargo).was_called(1)\n        assert.spy(ctx.spawn.cargo).was_called_with {\n            \"install\",\n            \"--root\",\n            \".\",\n            { \"--git\", \"https://github.com/neovim/neovim\", \"--tag\", \"1.0.0\" },\n            vim.NIL, -- features\n            vim.NIL, -- locked\n            \"my-crate\",\n        }\n    end)\n\n    it(\"should install git rev source\", function()\n        local ctx = test_helpers.create_context()\n        ctx:execute(function()\n            cargo.install(\"my-crate\", \"16dfc89abd413c391e5b63ae5d132c22843ce9a7\", {\n                git = {\n                    url = \"https://github.com/neovim/neovim\",\n                    rev = true,\n                },\n            })\n        end)\n\n        assert.spy(ctx.spawn.cargo).was_called(1)\n        assert.spy(ctx.spawn.cargo).was_called_with {\n            \"install\",\n            \"--root\",\n            \".\",\n            { \"--git\", \"https://github.com/neovim/neovim\", \"--rev\", \"16dfc89abd413c391e5b63ae5d132c22843ce9a7\" },\n            vim.NIL, -- features\n            vim.NIL, -- locked\n            \"my-crate\",\n        }\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/managers/common_spec.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal common = require \"mason-core.installer.managers.common\"\nlocal installer = require \"mason-core.installer\"\nlocal match = require \"luassert.match\"\nlocal mock = require \"luassert.mock\"\nlocal spy = require \"luassert.spy\"\nlocal std = require \"mason-core.installer.managers.std\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"common manager :: download\", function()\n    it(\"should parse download files from common structure\", function()\n        local url_generator = _.format \"https://example.com/%s\"\n\n        assert.same(\n            {\n                {\n                    download_url = \"https://example.com/abc.jar\",\n                    out_file = \"abc.jar\",\n                },\n            },\n            common.parse_downloads({\n                file = \"abc.jar\",\n            }, url_generator)\n        )\n\n        assert.same(\n            {\n                {\n                    download_url = \"https://example.com/abc.jar\",\n                    out_file = \"lib/abc.jar\",\n                },\n            },\n            common.parse_downloads({\n                file = \"abc.jar:lib/\",\n            }, url_generator)\n        )\n\n        assert.same(\n            {\n                {\n                    download_url = \"https://example.com/abc.jar\",\n                    out_file = \"lib/abc.jar\",\n                },\n                {\n                    download_url = \"https://example.com/file.jar\",\n                    out_file = \"lib/nested/new-name.jar\",\n                },\n            },\n            common.parse_downloads({\n                file = { \"abc.jar:lib/\", \"file.jar:lib/nested/new-name.jar\" },\n            }, url_generator)\n        )\n    end)\n\n    it(\"should download files\", function()\n        local ctx = test_helpers.create_context()\n        stub(std, \"download_file\", mockx.returns(Result.success()))\n        stub(std, \"unpack\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return common.download_files(ctx, {\n                { out_file = \"file.jar\", download_url = \"https://example.com/file.jar\" },\n                { out_file = \"LICENSE.md\", download_url = \"https://example.com/LICENSE\" },\n            })\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(std.download_file).was_called(2)\n        assert.spy(std.download_file).was_called_with(\"https://example.com/file.jar\", \"file.jar\")\n        assert.spy(std.download_file).was_called_with(\"https://example.com/LICENSE\", \"LICENSE.md\")\n        assert.spy(std.unpack).was_called(2)\n        assert.spy(std.unpack).was_called_with \"file.jar\"\n        assert.spy(std.unpack).was_called_with \"LICENSE.md\"\n    end)\n\n    it(\"should download files to specified directory\", function()\n        local ctx = test_helpers.create_context()\n        stub(std, \"download_file\", mockx.returns(Result.success()))\n        stub(std, \"unpack\", mockx.returns(Result.success()))\n        stub(ctx.fs, \"mkdirp\")\n\n        local result = ctx:execute(function()\n            return common.download_files(ctx, {\n                { out_file = \"lib/file.jar\", download_url = \"https://example.com/file.jar\" },\n                { out_file = \"doc/LICENSE.md\", download_url = \"https://example.com/LICENSE\" },\n                { out_file = \"nested/path/to/file\", download_url = \"https://example.com/some-file\" },\n            })\n        end)\n\n        assert.is_true(result:is_success())\n\n        assert.spy(ctx.fs.mkdirp).was_called(3)\n        assert.spy(ctx.fs.mkdirp).was_called_with(match.is_ref(ctx.fs), \"lib\")\n        assert.spy(ctx.fs.mkdirp).was_called_with(match.is_ref(ctx.fs), \"doc\")\n        assert.spy(ctx.fs.mkdirp).was_called_with(match.is_ref(ctx.fs), \"nested/path/to\")\n    end)\nend)\n\ndescribe(\"common manager :: build\", function()\n    it(\"should run build instruction\", function()\n        local ctx = test_helpers.create_context()\n        local uv = require \"mason-core.async.uv\"\n        spy.on(ctx, \"promote_cwd\")\n        stub(uv, \"write\")\n        stub(uv, \"shutdown\")\n        stub(uv, \"close\")\n        local stdin = mock.new()\n        stub(\n            ctx.spawn,\n            \"bash\", ---@param args SpawnArgs\n            function(args)\n                args.on_spawn(mock.new(), { stdin })\n                return Result.success()\n            end\n        )\n\n        local result = ctx:execute(function()\n            return common.run_build_instruction {\n                run = [[npm install && npm run compile]],\n                env = {\n                    MASON_VERSION = \"2023-03-09\",\n                    SOME_VALUE = \"here\",\n                },\n            }\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(ctx.promote_cwd).was_called(0)\n        assert.spy(ctx.spawn.bash).was_called(1)\n        assert.spy(ctx.spawn.bash).was_called_with(match.tbl_containing {\n            on_spawn = match.is_function(),\n            env = match.same {\n                MASON_VERSION = \"2023-03-09\",\n                SOME_VALUE = \"here\",\n            },\n        })\n        assert.spy(uv.write).was_called(2)\n        assert.spy(uv.write).was_called_with(stdin, \"set -euxo pipefail;\\n\")\n        assert.spy(uv.write).was_called_with(stdin, \"npm install && npm run compile\")\n        assert.spy(uv.shutdown).was_called_with(stdin)\n        assert.spy(uv.close).was_called_with(stdin)\n    end)\n\n    it(\"should promote cwd if not staged\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"promote_cwd\")\n        stub(ctx.spawn, \"bash\", mockx.returns(Result.success()))\n\n        local result = ctx:execute(function()\n            return common.run_build_instruction {\n                run = \"make\",\n                staged = false,\n            }\n        end)\n\n        assert.is_true(result:is_success())\n        assert.spy(ctx.promote_cwd).was_called(1)\n        assert.spy(ctx.spawn.bash).was_called(1)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/managers/composer_spec.lua",
    "content": "local composer = require \"mason-core.installer.managers.composer\"\nlocal match = require \"luassert.match\"\nlocal spy = require \"luassert.spy\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"composer manager\", function()\n    it(\"should install\", function()\n        local ctx = test_helpers.create_context()\n        ctx:execute(function()\n            composer.install(\"my-package\", \"1.0.0\")\n        end)\n\n        assert.spy(ctx.spawn.composer).was_called(2)\n        assert.spy(ctx.spawn.composer).was_called_with {\n            \"init\",\n            \"--no-interaction\",\n            \"--stability=stable\",\n        }\n        assert.spy(ctx.spawn.composer).was_called_with {\n            \"require\",\n            \"my-package:1.0.0\",\n        }\n    end)\n\n    it(\"should write output\", function()\n        local ctx = test_helpers.create_context()\n        spy.on(ctx.stdio_sink, \"stdout\")\n\n        ctx:execute(function()\n            composer.install(\"my-package\", \"1.0.0\")\n        end)\n\n        assert\n            .spy(ctx.stdio_sink.stdout)\n            .was_called_with(match.is_ref(ctx.stdio_sink), \"Installing composer package my-package@1.0.0…\\n\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/managers/gem_spec.lua",
    "content": "local gem = require \"mason-core.installer.managers.gem\"\nlocal match = require \"luassert.match\"\nlocal spy = require \"luassert.spy\"\nlocal test_helper = require \"mason-test.helpers\"\n\ndescribe(\"gem manager\", function()\n    it(\"should install\", function()\n        local ctx = test_helper.create_context()\n\n        local result = ctx:execute(function()\n            return gem.install(\"my-gem\", \"1.0.0\")\n        end)\n        assert.is_true(result:is_success())\n\n        assert.spy(ctx.spawn.gem).was_called(1)\n        assert.spy(ctx.spawn.gem).was_called_with {\n            \"install\",\n            \"--no-user-install\",\n            \"--no-format-executable\",\n            \"--install-dir=.\",\n            \"--bindir=bin\",\n            \"--no-document\",\n            \"my-gem:1.0.0\",\n            vim.NIL, -- extra_packages\n            env = {\n                GEM_HOME = ctx.location:staging \"dummy\",\n            },\n        }\n    end)\n\n    it(\"should write output\", function()\n        local ctx = test_helper.create_context()\n        spy.on(ctx.stdio_sink, \"stdout\")\n        ctx:execute(function()\n            gem.install(\"my-gem\", \"1.0.0\")\n        end)\n\n        assert\n            .spy(ctx.stdio_sink.stdout)\n            .was_called_with(match.is_ref(ctx.stdio_sink), \"Installing gem my-gem@1.0.0…\\n\")\n    end)\n\n    it(\"should install extra packages\", function()\n        local ctx = test_helper.create_context()\n        ctx:execute(function()\n            gem.install(\"my-gem\", \"1.0.0\", {\n                extra_packages = { \"extra-gem\" },\n            })\n        end)\n\n        assert.spy(ctx.spawn.gem).was_called(1)\n        assert.spy(ctx.spawn.gem).was_called_with {\n            \"install\",\n            \"--no-user-install\",\n            \"--no-format-executable\",\n            \"--install-dir=.\",\n            \"--bindir=bin\",\n            \"--no-document\",\n            \"my-gem:1.0.0\",\n            { \"extra-gem\" },\n            env = {\n                GEM_HOME = ctx.cwd:get(),\n            },\n        }\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/managers/golang_spec.lua",
    "content": "local golang = require \"mason-core.installer.managers.golang\"\nlocal match = require \"luassert.match\"\nlocal spy = require \"luassert.spy\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"golang manager\", function()\n    it(\"should install\", function()\n        local ctx = test_helpers.create_context()\n\n        ctx:execute(function()\n            golang.install(\"my-golang\", \"1.0.0\")\n        end)\n\n        assert.spy(ctx.spawn.go).was_called(1)\n        assert.spy(ctx.spawn.go).was_called_with {\n            \"install\",\n            \"-v\",\n            \"my-golang@1.0.0\",\n            env = {\n                GOBIN = ctx.cwd:get(),\n            },\n        }\n    end)\n\n    it(\"should write output\", function()\n        local ctx = test_helpers.create_context()\n        spy.on(ctx.stdio_sink, \"stdout\")\n\n        ctx:execute(function()\n            golang.install(\"my-golang\", \"1.0.0\")\n        end)\n\n        assert\n            .spy(ctx.stdio_sink.stdout)\n            .was_called_with(match.is_ref(ctx.stdio_sink), \"Installing go package my-golang@1.0.0…\\n\")\n    end)\n\n    it(\"should install extra packages\", function()\n        local ctx = test_helpers.create_context()\n        ctx:execute(function()\n            golang.install(\"my-golang\", \"1.0.0\", {\n                extra_packages = { \"extra\", \"package\" },\n            })\n        end)\n\n        assert.spy(ctx.spawn.go).was_called(3)\n        assert.spy(ctx.spawn.go).was_called_with {\n            \"install\",\n            \"-v\",\n            \"my-golang@1.0.0\",\n            env = {\n                GOBIN = ctx.cwd:get(),\n            },\n        }\n        assert.spy(ctx.spawn.go).was_called_with {\n            \"install\",\n            \"-v\",\n            \"extra@latest\",\n            env = {\n                GOBIN = ctx.cwd:get(),\n            },\n        }\n        assert.spy(ctx.spawn.go).was_called_with {\n            \"install\",\n            \"-v\",\n            \"package@latest\",\n            env = {\n                GOBIN = ctx.cwd:get(),\n            },\n        }\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/managers/luarocks_spec.lua",
    "content": "local luarocks = require \"mason-core.installer.managers.luarocks\"\nlocal match = require \"luassert.match\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"luarocks manager\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should install\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"promote_cwd\")\n        ctx:execute(function()\n            luarocks.install(\"my-rock\", \"1.0.0\")\n        end)\n\n        assert.spy(ctx.promote_cwd).was_called(1)\n        assert.spy(ctx.spawn.luarocks).was_called(1)\n        assert.spy(ctx.spawn.luarocks).was_called_with {\n            \"install\",\n            { \"--tree\", ctx.cwd:get() },\n            vim.NIL, -- dev\n            vim.NIL, -- server\n            { \"my-rock\", \"1.0.0\" },\n        }\n    end)\n\n    it(\"should install dev mode\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"promote_cwd\")\n        ctx:execute(function()\n            luarocks.install(\"my-rock\", \"1.0.0\", {\n                dev = true,\n            })\n        end)\n\n        assert.spy(ctx.promote_cwd).was_called(1)\n        assert.spy(ctx.spawn.luarocks).was_called(1)\n        assert.spy(ctx.spawn.luarocks).was_called_with {\n            \"install\",\n            { \"--tree\", ctx.cwd:get() },\n            \"--dev\",\n            vim.NIL, -- server\n            { \"my-rock\", \"1.0.0\" },\n        }\n    end)\n\n    it(\"should install using provided server\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"promote_cwd\")\n        ctx:execute(function()\n            luarocks.install(\"my-rock\", \"1.0.0\", {\n                server = \"https://luarocks.org/dev\",\n            })\n        end)\n\n        assert.spy(ctx.promote_cwd).was_called(1)\n        assert.spy(ctx.spawn.luarocks).was_called(1)\n        assert.spy(ctx.spawn.luarocks).was_called_with {\n            \"install\",\n            { \"--tree\", ctx.cwd:get() },\n            vim.NIL, -- dev\n            \"--server=https://luarocks.org/dev\",\n            { \"my-rock\", \"1.0.0\" },\n        }\n    end)\n\n    it(\"should write output\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"promote_cwd\")\n        spy.on(ctx.stdio_sink, \"stdout\")\n        ctx:execute(function()\n            luarocks.install(\"my-rock\", \"1.0.0\")\n        end)\n\n        assert\n            .spy(ctx.stdio_sink.stdout)\n            .was_called_with(match.is_ref(ctx.stdio_sink), \"Installing luarocks package my-rock@1.0.0…\\n\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/managers/npm_spec.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal match = require \"luassert.match\"\nlocal npm = require \"mason-core.installer.managers.npm\"\nlocal spawn = require \"mason-core.spawn\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"npm manager\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should init package.json\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"append_file\")\n        stub(spawn, \"npm\")\n        spawn.npm.returns(Result.success {})\n        spawn.npm.on_call_with({ \"version\", \"--json\" }).returns(Result.success {\n            stdout = [[ { \"npm\": \"8.1.0\" } ]],\n        })\n        ctx:execute(function()\n            npm.init()\n        end)\n\n        assert.spy(ctx.spawn.npm).was_called(1)\n        assert.spy(ctx.spawn.npm).was_called_with {\n            \"init\",\n            \"--yes\",\n            \"--scope=mason\",\n        }\n        assert.spy(ctx.fs.append_file).was_called(1)\n        assert.spy(ctx.fs.append_file).was_called_with(match.is_ref(ctx.fs), \".npmrc\", \"\\nglobal-style=true\")\n    end)\n\n    it(\"should use install-strategy on npm >= 9\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"append_file\")\n        stub(spawn, \"npm\")\n        spawn.npm.returns(Result.success {})\n        spawn.npm.on_call_with({ \"version\", \"--json\" }).returns(Result.success {\n            stdout = [[ { \"npm\": \"9.1.0\" } ]],\n        })\n        ctx:execute(function()\n            npm.init()\n        end)\n\n        assert.spy(ctx.spawn.npm).was_called(1)\n        assert.spy(ctx.spawn.npm).was_called_with {\n            \"init\",\n            \"--yes\",\n            \"--scope=mason\",\n        }\n        assert.spy(ctx.fs.append_file).was_called_with(match.is_ref(ctx.fs), \".npmrc\", \"\\ninstall-strategy=shallow\")\n    end)\n\n    it(\"should install\", function()\n        local ctx = test_helpers.create_context()\n        ctx:execute(function()\n            npm.install(\"my-package\", \"1.0.0\")\n        end)\n\n        assert.spy(ctx.spawn.npm).was_called(1)\n        assert.spy(ctx.spawn.npm).was_called_with {\n            \"install\",\n            \"my-package@1.0.0\",\n            vim.NIL, -- extra_packages\n        }\n    end)\n\n    it(\"should install extra packages\", function()\n        local ctx = test_helpers.create_context()\n        ctx:execute(function()\n            npm.install(\"my-package\", \"1.0.0\", {\n                extra_packages = { \"extra-package\" },\n            })\n        end)\n\n        assert.spy(ctx.spawn.npm).was_called(1)\n        assert.spy(ctx.spawn.npm).was_called_with {\n            \"install\",\n            \"my-package@1.0.0\",\n            { \"extra-package\" },\n        }\n    end)\n\n    it(\"should write output\", function()\n        local ctx = test_helpers.create_context()\n        spy.on(ctx.stdio_sink, \"stdout\")\n\n        ctx:execute(function()\n            npm.install(\"my-package\", \"1.0.0\")\n        end)\n\n        assert\n            .spy(ctx.stdio_sink.stdout)\n            .was_called_with(match.is_ref(ctx.stdio_sink), \"Installing npm package my-package@1.0.0…\\n\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/managers/nuget_spec.lua",
    "content": "local match = require \"luassert.match\"\nlocal nuget = require \"mason-core.installer.managers.nuget\"\nlocal spy = require \"luassert.spy\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"nuget manager\", function()\n    it(\"should install\", function()\n        local ctx = test_helpers.create_context()\n        ctx:execute(function()\n            nuget.install(\"nuget-package\", \"1.0.0\")\n        end)\n\n        assert.spy(ctx.spawn.dotnet).was_called(1)\n        assert.spy(ctx.spawn.dotnet).was_called_with {\n            \"tool\",\n            \"update\",\n            \"--tool-path\",\n            \".\",\n            { \"--version\", \"1.0.0\" },\n            \"nuget-package\",\n        }\n    end)\n\n    it(\"should write output\", function()\n        local ctx = test_helpers.create_context()\n        spy.on(ctx.stdio_sink, \"stdout\")\n\n        ctx:execute(function()\n            nuget.install(\"nuget-package\", \"1.0.0\")\n        end)\n\n        assert\n            .spy(ctx.stdio_sink.stdout)\n            .was_called_with(match.is_ref(ctx.stdio_sink), \"Installing nuget package nuget-package@1.0.0…\\n\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/managers/opam_spec.lua",
    "content": "local match = require \"luassert.match\"\nlocal opam = require \"mason-core.installer.managers.opam\"\nlocal spy = require \"luassert.spy\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"opam manager\", function()\n    it(\"should install\", function()\n        local ctx = test_helpers.create_context()\n\n        ctx:execute(function()\n            opam.install(\"opam-package\", \"1.0.0\")\n        end)\n\n        assert.spy(ctx.spawn.opam).was_called(1)\n        assert.spy(ctx.spawn.opam).was_called_with {\n            \"install\",\n            \"--destdir=.\",\n            \"--yes\",\n            \"--verbose\",\n            \"opam-package.1.0.0\",\n        }\n    end)\n\n    it(\"should write output\", function()\n        local ctx = test_helpers.create_context()\n        spy.on(ctx.stdio_sink, \"stdout\")\n\n        ctx:execute(function()\n            opam.install(\"opam-package\", \"1.0.0\")\n        end)\n\n        assert\n            .spy(ctx.stdio_sink.stdout)\n            .was_called_with(match.is_ref(ctx.stdio_sink), \"Installing opam package opam-package@1.0.0…\\n\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/managers/powershell_spec.lua",
    "content": "local a = require \"mason-core.async\"\nlocal match = require \"luassert.match\"\nlocal mock = require \"luassert.mock\"\nlocal spawn = require \"mason-core.spawn\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\n\ndescribe(\"powershell manager\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    local function powershell()\n        package.loaded[\"mason-core.installer.managers.powershell\"] = nil\n        return require \"mason-core.installer.managers.powershell\"\n    end\n\n    it(\"should use pwsh if available\", function()\n        stub(spawn, \"pwsh\", function() end)\n        stub(spawn, \"powershell\", function() end)\n        stub(vim.fn, \"executable\")\n        vim.fn.executable.on_call_with(\"pwsh\").returns(1)\n\n        powershell().command \"echo 'Is this bash?'\"\n\n        assert.spy(spawn.pwsh).was_called(1)\n        assert.spy(spawn.powershell).was_called(0)\n    end)\n\n    it(\"should use powershell if pwsh is not available\", function()\n        stub(spawn, \"pwsh\", function() end)\n        stub(spawn, \"powershell\", function() end)\n        stub(vim.fn, \"executable\")\n        vim.fn.executable.on_call_with(\"pwsh\").returns(0)\n\n        local powershell = powershell()\n        a.run_blocking(powershell.command, \"echo 'Is this bash?'\")\n\n        assert.spy(spawn.pwsh).was_called(0)\n        assert.spy(spawn.powershell).was_called(1)\n    end)\n\n    it(\"should use the provided spawner for commands\", function()\n        spy.on(spawn, \"pwsh\")\n        local custom_spawn = mock.new { pwsh = mockx.just_runs }\n        stub(vim.fn, \"executable\")\n        vim.fn.executable.on_call_with(\"pwsh\").returns(1)\n        powershell().command(\"echo 'Is this bash?'\", {}, custom_spawn)\n\n        assert.spy(spawn.pwsh).was_called(0)\n        assert.spy(custom_spawn.pwsh).was_called(1)\n    end)\n\n    it(\"should provide default powershell options via command interface\", function()\n        stub(spawn, \"pwsh\", function() end)\n        stub(vim.fn, \"executable\")\n        vim.fn.executable.on_call_with(\"pwsh\").returns(1)\n\n        powershell().command \"echo 'Is this bash?'\"\n\n        assert.spy(spawn.pwsh).was_called(1)\n        assert.spy(spawn.pwsh).was_called_with(match.tbl_containing {\n            \"-NoProfile\",\n            \"-NonInteractive\",\n            \"-Command\",\n            [[ $ErrorActionPreference = \"Stop\";  $ProgressPreference = 'SilentlyContinue';  [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; echo 'Is this bash?']],\n        })\n    end)\n\n    it(\"should close stdin\", function()\n        local stdin = {\n            close = spy.new(),\n        }\n        stub(\n            spawn,\n            \"pwsh\",\n            ---@param args SpawnArgs\n            function(args)\n                args.on_spawn(mock.new(), {\n                    stdin,\n                }, 1)\n            end\n        )\n        stub(vim.fn, \"executable\")\n        vim.fn.executable.on_call_with(\"pwsh\").returns(1)\n\n        powershell().command \"Powershell-Command\"\n\n        assert.spy(spawn.pwsh).was_called(1)\n        assert.spy(stdin.close).was_called(1)\n        assert.spy(stdin.close).was_called_with(match.is_ref(stdin))\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/managers/pypi_spec.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal match = require \"luassert.match\"\nlocal path = require \"mason-core.path\"\nlocal providers = require \"mason-core.providers\"\nlocal pypi = require \"mason-core.installer.managers.pypi\"\nlocal spawn = require \"mason-core.spawn\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\n---@param ctx InstallContext\nlocal function venv_py(ctx)\n    return path.concat {\n        ctx.cwd:get(),\n        \"venv\",\n        \"bin\",\n        \"python\",\n    }\nend\n\ndescribe(\"pypi manager\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n        stub(spawn, \"python3\", mockx.returns(Result.success()))\n        spawn.python3.on_call_with({ \"--version\" }).returns(Result.success { stdout = \"Python 3.11.0\" })\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should init venv without upgrading pip\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"promote_cwd\")\n        stub(providers.pypi, \"get_supported_python_versions\", mockx.returns(Result.failure()))\n\n        ctx:execute(function()\n            pypi.init { package = { name = \"cmake-language-server\", version = \"0.1.10\" }, upgrade_pip = false }\n        end)\n\n        assert.spy(ctx.promote_cwd).was_called(1)\n        assert.spy(ctx.spawn.python3).was_called(1)\n        assert.spy(ctx.spawn.python3).was_called_with {\n            \"-m\",\n            \"venv\",\n            \"--system-site-packages\",\n            \"venv\",\n        }\n    end)\n\n    it(\"should init venv and upgrade pip\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"promote_cwd\")\n        stub(ctx.fs, \"file_exists\")\n        stub(providers.pypi, \"get_supported_python_versions\", mockx.returns(Result.failure()))\n        ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), \"venv/bin/python\").returns(true)\n\n        ctx:execute(function()\n            pypi.init {\n                package = { name = \"cmake-language-server\", version = \"0.1.10\" },\n                upgrade_pip = true,\n                install_extra_args = { \"--proxy\", \"http://localhost\" },\n            }\n        end)\n\n        assert.spy(ctx.promote_cwd).was_called(1)\n        assert.spy(ctx.spawn.python3).was_called(1)\n        assert.spy(ctx.spawn.python3).was_called_with {\n            \"-m\",\n            \"venv\",\n            \"--system-site-packages\",\n            \"venv\",\n        }\n        assert.spy(ctx.spawn[venv_py(ctx)]).was_called(1)\n        assert.spy(ctx.spawn[venv_py(ctx)]).was_called_with {\n            \"-m\",\n            \"pip\",\n            \"--disable-pip-version-check\",\n            \"install\",\n            \"--no-user\",\n            \"--ignore-installed\",\n            { \"--proxy\", \"http://localhost\" },\n            { \"pip\" },\n        }\n    end)\n\n    it(\"should find versioned candidates during init\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx, \"promote_cwd\")\n        stub(ctx.fs, \"file_exists\")\n        stub(providers.pypi, \"get_supported_python_versions\", mockx.returns(Result.success \">=3.12\"))\n        stub(vim.fn, \"executable\")\n        vim.fn.executable.on_call_with(\"python3.12\").returns(1)\n        stub(spawn, \"python3.12\")\n        spawn[\"python3.12\"].on_call_with({ \"--version\" }).returns(Result.success { stdout = \"Python 3.12.0\" })\n        ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), \"venv/bin/python\").returns(true)\n\n        ctx:execute(function()\n            pypi.init {\n                package = { name = \"cmake-language-server\", version = \"0.1.10\" },\n                upgrade_pip = false,\n                install_extra_args = {},\n            }\n        end)\n\n        assert.spy(ctx.promote_cwd).was_called(1)\n        assert.spy(ctx.spawn[\"python3.12\"]).was_called(1)\n        assert.spy(ctx.spawn[\"python3.12\"]).was_called_with {\n            \"-m\",\n            \"venv\",\n            \"--system-site-packages\",\n            \"venv\",\n        }\n    end)\n\n    it(\"should error if unable to find a suitable python3 version\", function()\n        local ctx = test_helpers.create_context()\n        spy.on(ctx.stdio_sink, \"stderr\")\n        stub(ctx, \"promote_cwd\")\n        stub(ctx.fs, \"file_exists\")\n        stub(providers.pypi, \"get_supported_python_versions\", mockx.returns(Result.success \">=3.8\"))\n        stub(vim.fn, \"executable\")\n        vim.fn.executable.on_call_with(\"python3.12\").returns(0)\n        vim.fn.executable.on_call_with(\"python3.11\").returns(0)\n        vim.fn.executable.on_call_with(\"python3.10\").returns(0)\n        vim.fn.executable.on_call_with(\"python3.9\").returns(0)\n        vim.fn.executable.on_call_with(\"python3.8\").returns(0)\n        stub(spawn, \"python3\", mockx.returns(Result.success()))\n        spawn.python3.on_call_with({ \"--version\" }).returns(Result.success { stdout = \"Python 3.5.0\" })\n\n        local result = ctx:execute(function()\n            return pypi.init {\n                package = { name = \"cmake-language-server\", version = \"0.1.10\" },\n                upgrade_pip = false,\n                install_extra_args = {},\n            }\n        end)\n\n        assert.same(\n            Result.failure \"Failed to find a python3 installation in PATH that meets the required versions (>=3.8). Found version: 3.5.0.\",\n            result\n        )\n        assert\n            .spy(ctx.stdio_sink.stderr)\n            .was_called_with(match.is_ref(ctx.stdio_sink), \"Run with :MasonInstall --force to bypass this version validation.\\n\")\n    end)\n\n    it(\n        \"should default to stock version if unable to find suitable versioned candidate during init and when force=true\",\n        function()\n            local ctx = test_helpers.create_context { install_opts = { force = true } }\n            spy.on(ctx.stdio_sink, \"stderr\")\n            stub(ctx, \"promote_cwd\")\n            stub(ctx.fs, \"file_exists\")\n            stub(providers.pypi, \"get_supported_python_versions\", mockx.returns(Result.success \">=3.8\"))\n            stub(vim.fn, \"executable\")\n            vim.fn.executable.on_call_with(\"python3.12\").returns(0)\n            vim.fn.executable.on_call_with(\"python3.11\").returns(0)\n            vim.fn.executable.on_call_with(\"python3.10\").returns(0)\n            vim.fn.executable.on_call_with(\"python3.9\").returns(0)\n            vim.fn.executable.on_call_with(\"python3.8\").returns(0)\n            stub(spawn, \"python3\", mockx.returns(Result.success()))\n            spawn.python3.on_call_with({ \"--version\" }).returns(Result.success { stdout = \"Python 3.5.0\" })\n\n            ctx:execute(function()\n                pypi.init {\n                    package = { name = \"cmake-language-server\", version = \"0.1.10\" },\n                    upgrade_pip = true,\n                    install_extra_args = { \"--proxy\", \"http://localhost\" },\n                }\n            end)\n\n            assert.spy(ctx.promote_cwd).was_called(1)\n            assert.spy(ctx.spawn.python3).was_called(1)\n            assert.spy(ctx.spawn.python3).was_called_with {\n                \"-m\",\n                \"venv\",\n                \"--system-site-packages\",\n                \"venv\",\n            }\n            assert.spy(ctx.stdio_sink.stderr).was_called_with(\n                match.is_ref(ctx.stdio_sink),\n                \"Warning: The resolved python3 version 3.5.0 is not compatible with the required Python versions: >=3.8.\\n\"\n            )\n        end\n    )\n\n    it(\"should prioritize stock python\", function()\n        local ctx = test_helpers.create_context { install_opts = { force = true } }\n        spy.on(ctx.stdio_sink, \"stderr\")\n        stub(ctx, \"promote_cwd\")\n        stub(ctx.fs, \"file_exists\")\n        stub(providers.pypi, \"get_supported_python_versions\", mockx.returns(Result.success \">=3.8\"))\n        stub(vim.fn, \"executable\")\n        vim.fn.executable.on_call_with(\"python3.12\").returns(1)\n        stub(spawn, \"python3\", mockx.returns(Result.success()))\n        spawn.python3.on_call_with({ \"--version\" }).returns(Result.success { stdout = \"Python 3.8.0\" })\n\n        ctx:execute(function()\n            pypi.init {\n                package = { name = \"cmake-language-server\", version = \"0.1.10\" },\n                upgrade_pip = true,\n                install_extra_args = { \"--proxy\", \"http://localhost\" },\n            }\n        end)\n\n        assert.spy(ctx.promote_cwd).was_called(1)\n        assert.spy(ctx.spawn.python3).was_called(1)\n        assert.spy(ctx.spawn[\"python3.12\"]).was_called(0)\n        assert.spy(ctx.spawn.python3).was_called_with {\n            \"-m\",\n            \"venv\",\n            \"--system-site-packages\",\n            \"venv\",\n        }\n    end)\n\n    it(\"should install\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"file_exists\")\n        ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), \"venv/bin/python\").returns(true)\n\n        ctx:execute(function()\n            pypi.install(\"pypi-package\", \"1.0.0\")\n        end)\n\n        assert.spy(ctx.spawn[venv_py(ctx)]).was_called(1)\n        assert.spy(ctx.spawn[venv_py(ctx)]).was_called_with {\n            \"-m\",\n            \"pip\",\n            \"--disable-pip-version-check\",\n            \"install\",\n            \"--no-user\",\n            \"--ignore-installed\",\n            vim.NIL, -- install_extra_args\n            {\n                \"pypi-package==1.0.0\",\n                vim.NIL, -- extra_packages\n            },\n        }\n    end)\n\n    it(\"should write output\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"file_exists\")\n        ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), \"venv/bin/python\").returns(true)\n        spy.on(ctx.stdio_sink, \"stdout\")\n\n        ctx:execute(function()\n            pypi.install(\"pypi-package\", \"1.0.0\")\n        end)\n\n        assert\n            .spy(ctx.stdio_sink.stdout)\n            .was_called_with(match.is_ref(ctx.stdio_sink), \"Installing pip package pypi-package@1.0.0…\\n\")\n    end)\n\n    it(\"should install extra specifier\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"file_exists\")\n        ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), \"venv/bin/python\").returns(true)\n\n        ctx:execute(function()\n            pypi.install(\"pypi-package\", \"1.0.0\", {\n                extra = \"lsp\",\n            })\n        end)\n\n        assert.spy(ctx.spawn[venv_py(ctx)]).was_called(1)\n        assert.spy(ctx.spawn[venv_py(ctx)]).was_called_with {\n            \"-m\",\n            \"pip\",\n            \"--disable-pip-version-check\",\n            \"install\",\n            \"--no-user\",\n            \"--ignore-installed\",\n            vim.NIL, -- install_extra_args\n            {\n                \"pypi-package[lsp]==1.0.0\",\n                vim.NIL, -- extra_packages\n            },\n        }\n    end)\n\n    it(\"should install extra packages\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"file_exists\")\n        ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), \"venv/bin/python\").returns(true)\n        ctx:execute(function()\n            pypi.install(\"pypi-package\", \"1.0.0\", {\n                extra_packages = { \"extra-package\" },\n                install_extra_args = { \"--proxy\", \"http://localhost:9000\" },\n            })\n        end)\n\n        assert.spy(ctx.spawn[venv_py(ctx)]).was_called(1)\n        assert.spy(ctx.spawn[venv_py(ctx)]).was_called_with {\n            \"-m\",\n            \"pip\",\n            \"--disable-pip-version-check\",\n            \"install\",\n            \"--no-user\",\n            \"--ignore-installed\",\n            { \"--proxy\", \"http://localhost:9000\" },\n            {\n                \"pypi-package==1.0.0\",\n                { \"extra-package\" },\n            },\n        }\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/installer/managers/std_spec.lua",
    "content": "local match = require \"luassert.match\"\nlocal std = require \"mason-core.installer.managers.std\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"std unpack [Unix]\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should unpack .gz\", function()\n        local ctx = test_helpers.create_context()\n        ctx:execute(function()\n            std.unpack \"file.gz\"\n        end)\n\n        assert.spy(ctx.spawn.gzip).was_called(1)\n        assert.spy(ctx.spawn.gzip).was_called_with { \"-d\", \"file.gz\" }\n    end)\n\n    describe(\"tar\", function()\n        before_each(function()\n            stub(vim.fn, \"executable\")\n            vim.fn.executable.on_call_with(\"gtar\").returns(0)\n        end)\n\n        it(\"should use gtar if available\", function()\n            local ctx = test_helpers.create_context()\n            stub(ctx.fs, \"unlink\")\n            stub(vim.fn, \"executable\")\n            vim.fn.executable.on_call_with(\"gtar\").returns(1)\n\n            ctx:execute(function()\n                std.unpack \"file.tar.gz\"\n            end)\n\n            assert.spy(ctx.spawn.gtar).was_called(1)\n            assert.spy(ctx.spawn.gtar).was_called_with { \"--no-same-owner\", \"-xvf\", \"file.tar.gz\" }\n        end)\n\n        it(\"should unpack .tar\", function()\n            local ctx = test_helpers.create_context()\n            stub(ctx.fs, \"unlink\")\n            ctx:execute(function()\n                std.unpack \"file.tar\"\n            end)\n\n            assert.spy(ctx.spawn.tar).was_called(1)\n            assert.spy(ctx.spawn.tar).was_called_with { \"--no-same-owner\", \"-xvf\", \"file.tar\" }\n            assert.spy(ctx.fs.unlink).was_called(1)\n            assert.spy(ctx.fs.unlink).was_called_with(match.is_ref(ctx.fs), \"file.tar\")\n        end)\n\n        it(\"should unpack .tar.bz2\", function()\n            local ctx = test_helpers.create_context()\n            stub(ctx.fs, \"unlink\")\n            ctx:execute(function()\n                std.unpack \"file.tar.bz2\"\n            end)\n\n            assert.spy(ctx.spawn.tar).was_called(1)\n            assert.spy(ctx.spawn.tar).was_called_with { \"--no-same-owner\", \"-xvf\", \"file.tar.bz2\" }\n            assert.spy(ctx.fs.unlink).was_called(1)\n            assert.spy(ctx.fs.unlink).was_called_with(match.is_ref(ctx.fs), \"file.tar.bz2\")\n        end)\n\n        it(\"should unpack .tar.gz\", function()\n            local ctx = test_helpers.create_context()\n            stub(ctx.fs, \"unlink\")\n            ctx:execute(function()\n                std.unpack \"file.tar.gz\"\n            end)\n\n            assert.spy(ctx.spawn.tar).was_called(1)\n            assert.spy(ctx.spawn.tar).was_called_with { \"--no-same-owner\", \"-xvf\", \"file.tar.gz\" }\n            assert.spy(ctx.fs.unlink).was_called(1)\n            assert.spy(ctx.fs.unlink).was_called_with(match.is_ref(ctx.fs), \"file.tar.gz\")\n        end)\n\n        it(\"should unpack .tar.xz\", function()\n            local ctx = test_helpers.create_context()\n            stub(ctx.fs, \"unlink\")\n            ctx:execute(function()\n                std.unpack \"file.tar.xz\"\n            end)\n\n            assert.spy(ctx.spawn.tar).was_called(1)\n            assert.spy(ctx.spawn.tar).was_called_with { \"--no-same-owner\", \"-xvf\", \"file.tar.xz\" }\n            assert.spy(ctx.fs.unlink).was_called(1)\n            assert.spy(ctx.fs.unlink).was_called_with(match.is_ref(ctx.fs), \"file.tar.xz\")\n        end)\n\n        it(\"should unpack .tar.zst\", function()\n            local ctx = test_helpers.create_context()\n            stub(ctx.fs, \"unlink\")\n            ctx:execute(function()\n                std.unpack \"file.tar.zst\"\n            end)\n\n            assert.spy(ctx.spawn.tar).was_called(1)\n            assert.spy(ctx.spawn.tar).was_called_with { \"--no-same-owner\", \"-xvf\", \"file.tar.zst\" }\n            assert.spy(ctx.fs.unlink).was_called(1)\n            assert.spy(ctx.fs.unlink).was_called_with(match.is_ref(ctx.fs), \"file.tar.zst\")\n        end)\n    end)\n\n    it(\"should unpack .vsix\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"unlink\")\n        ctx:execute(function()\n            std.unpack \"file.vsix\"\n        end)\n\n        assert.spy(ctx.spawn.unzip).was_called(1)\n        assert.spy(ctx.spawn.unzip).was_called_with { \"-d\", \".\", \"file.vsix\" }\n        assert.spy(ctx.fs.unlink).was_called(1)\n        assert.spy(ctx.fs.unlink).was_called_with(match.is_ref(ctx.fs), \"file.vsix\")\n    end)\n\n    it(\"should unpack .zip\", function()\n        local ctx = test_helpers.create_context()\n        stub(ctx.fs, \"unlink\")\n        ctx:execute(function()\n            std.unpack \"file.zip\"\n        end)\n\n        assert.spy(ctx.spawn.unzip).was_called(1)\n        assert.spy(ctx.spawn.unzip).was_called_with { \"-d\", \".\", \"file.zip\" }\n        assert.spy(ctx.fs.unlink).was_called(1)\n        assert.spy(ctx.fs.unlink).was_called_with(match.is_ref(ctx.fs), \"file.zip\")\n    end)\nend)\n\ndescribe(\"std clone\", function()\n    it(\"should clone\", function()\n        local ctx = test_helpers.create_context()\n        ctx:execute(function()\n            std.clone \"https://github.com/mason-org/mason.nvim\"\n        end)\n\n        assert.spy(ctx.spawn.git).was_called(1)\n        assert.spy(ctx.spawn.git).was_called_with {\n            \"clone\",\n            \"--depth\",\n            \"1\",\n            vim.NIL, -- recursive\n            \"https://github.com/mason-org/mason.nvim\",\n            \".\",\n        }\n    end)\n\n    it(\"should clone and checkout rev\", function()\n        local ctx = test_helpers.create_context()\n        ctx:execute(function()\n            std.clone(\"https://github.com/mason-org/mason.nvim\", {\n                rev = \"e1fd03b1856cb5ad8425f49e18353dc524b02f91\",\n                recursive = true,\n            })\n        end)\n\n        assert.spy(ctx.spawn.git).was_called(3)\n        assert.spy(ctx.spawn.git).was_called_with {\n            \"clone\",\n            \"--depth\",\n            \"1\",\n            \"--recursive\",\n            \"https://github.com/mason-org/mason.nvim\",\n            \".\",\n        }\n        assert\n            .spy(ctx.spawn.git)\n            .was_called_with { \"fetch\", \"--depth\", \"1\", \"origin\", \"e1fd03b1856cb5ad8425f49e18353dc524b02f91\" }\n        assert.spy(ctx.spawn.git).was_called_with { \"checkout\", \"--quiet\", \"FETCH_HEAD\" }\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/optional_spec.lua",
    "content": "local Optional = require \"mason-core.optional\"\nlocal Result = require \"mason-core.result\"\nlocal spy = require \"luassert.spy\"\n\ndescribe(\"Optional.of_nilable\", function()\n    it(\"should create empty optionals\", function()\n        local empty = Optional.empty()\n        assert.is_false(empty:is_present())\n    end)\n\n    it(\"should create non-empty optionals\", function()\n        local empty = Optional.of_nilable \"value\"\n        assert.is_true(empty:is_present())\n    end)\n\n    it(\"should use memoized empty value\", function()\n        assert.is_true(Optional.empty() == Optional.empty())\n    end)\nend)\n\ndescribe(\"Optional.get()\", function()\n    it(\"should map non-empty values\", function()\n        local str = Optional.of_nilable(\"world!\")\n            :map(function(val)\n                return \"Hello \" .. val\n            end)\n            :get()\n        assert.equals(\"Hello world!\", str)\n    end)\n\n    it(\"should raise error when getting empty value\", function()\n        local err = assert.has_error(function()\n            Optional.empty():get()\n        end)\n        assert.equals(\"No value present.\", err)\n    end)\nend)\n\ndescribe(\"Optional.or_else()\", function()\n    it(\"should use .or_else() value if empty\", function()\n        local value = Optional.empty():or_else \"Hello!\"\n        assert.equals(\"Hello!\", value)\n    end)\n\n    it(\"should not use .or_else() value if not empty\", function()\n        local value = Optional.of_nilable(\"Good bye!\"):or_else \"Hello!\"\n        assert.equals(\"Good bye!\", value)\n    end)\nend)\n\ndescribe(\"Optional.if_present()\", function()\n    it(\"should not call .if_present() if value is empty\", function()\n        local present = spy.new()\n        Optional.empty():if_present(present)\n        assert.spy(present).was_not_called()\n    end)\n\n    it(\"should call .if_present() if value is not empty\", function()\n        local present = spy.new()\n        Optional.of_nilable(\"value\"):if_present(present)\n        assert.spy(present).was_called(1)\n        assert.spy(present).was_called_with \"value\"\n    end)\nend)\n\ndescribe(\"Optional.if_not_present()\", function()\n    it(\"should not call .if_not_present() if value is not empty\", function()\n        local present = spy.new()\n        Optional.of_nilable(\"value\"):if_not_present(present)\n        assert.spy(present).was_not_called()\n    end)\n\n    it(\"should call .if_not_present() if value is empty\", function()\n        local present = spy.new()\n        Optional.empty():if_not_present(present)\n        assert.spy(present).was_called(1)\n    end)\nend)\n\ndescribe(\"Optional.ok_or()\", function()\n    it(\"should return success variant if non-empty\", function()\n        local result = Optional.of_nilable(\"Hello world!\"):ok_or()\n        assert.is_true(getmetatable(result) == Result)\n        assert.equals(\"Hello world!\", result:get_or_nil())\n    end)\n\n    it(\"should return failure variant if empty\", function()\n        local result = Optional.empty():ok_or(function()\n            return \"I'm empty.\"\n        end)\n        assert.is_true(getmetatable(result) == Result)\n        assert.equals(\"I'm empty.\", result:err_or_nil())\n    end)\nend)\n\ndescribe(\"Optional.or_()\", function()\n    it(\"should run supplier if value is not present\", function()\n        local spy = spy.new(function()\n            return Optional.of \"Hello world!\"\n        end)\n        assert.same(Optional.of \"Hello world!\", Optional.empty():or_(spy))\n        assert.spy(spy).was_called(1)\n\n        assert.same(Optional.empty(), Optional.empty():or_(Optional.empty))\n    end)\n\n    it(\"should not run supplier if value is present\", function()\n        local spy = spy.new(function()\n            return Optional.of \"Hello world!\"\n        end)\n        assert.same(Optional.of \"Hello world!\", Optional.of(\"Hello world!\"):or_(spy))\n        assert.spy(spy).was_called(0)\n    end)\nend)\n\ndescribe(\"Optional.and_then()\", function()\n    it(\"should run supplier if value is present\", function()\n        local spy = spy.new(function(value)\n            return Optional.of((\"%s world!\"):format(value))\n        end)\n        assert.same(Optional.of \"Hello world!\", Optional.of(\"Hello\"):and_then(spy))\n        assert.spy(spy).was_called(1)\n\n        assert.same(\n            Optional.empty(),\n            Optional.empty():and_then(function()\n                return Optional.of \"Nothing.\"\n            end)\n        )\n    end)\n\n    it(\"should not run supplier if value is not present\", function()\n        local spy = spy.new(function()\n            return Optional.of \"Hello world!\"\n        end)\n        assert.same(Optional.empty(), Optional.empty():and_then(spy))\n        assert.spy(spy).was_called(0)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/package/package_spec.lua",
    "content": "local Pkg = require \"mason-core.package\"\nlocal a = require \"mason-core.async\"\nlocal match = require \"luassert.match\"\nlocal mock = require \"luassert.mock\"\nlocal receipt = require \"mason-core.receipt\"\nlocal registry = require \"mason-registry\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"Package ::\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n        local dummy = registry.get_package \"dummy\"\n        if dummy:is_installed() then\n            test_helpers.sync_uninstall(dummy)\n        end\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should parse package specifiers\", function()\n        local function parse(str)\n            local name, version = Pkg.Parse(str)\n            return { name, version }\n        end\n\n        assert.same({ \"rust-analyzer\", nil }, parse \"rust-analyzer\")\n        assert.same({ \"rust-analyzer\", \"\" }, parse \"rust-analyzer@\")\n        assert.same({ \"rust-analyzer\", \"nightly\" }, parse \"rust-analyzer@nightly\")\n    end)\n\n    if vim.fn.has \"nvim-0.11\" == 1 then\n        it(\"should validate spec\", function()\n            ---@type RegistryPackageSpec\n            local valid_spec = {\n                schema = \"registry+v1\",\n                name = \"Package name\",\n                description = \"Package description\",\n                homepage = \"https://example.com\",\n                categories = { \"LSP\" },\n                languages = { \"Rust\" },\n                licenses = {},\n                source = {\n                    id = \"pkg:mason/package@1\",\n                    install = function() end,\n                },\n            }\n            local function spec(fields)\n                return setmetatable(fields, { __index = valid_spec })\n            end\n            assert.equals(\n                \"name: expected string, got number\",\n                assert.has_error(function()\n                    Pkg:new(spec { name = 23 })\n                end)\n            )\n\n            assert.equals(\n                \"description: expected string, got number\",\n                assert.has_error(function()\n                    Pkg:new(spec { description = 23 })\n                end)\n            )\n\n            assert.equals(\n                \"homepage: expected string, got number\",\n                assert.has_error(function()\n                    Pkg:new(spec { homepage = 23 })\n                end)\n            )\n\n            assert.equals(\n                \"categories: expected table, got number\",\n                assert.has_error(function()\n                    Pkg:new(spec { categories = 23 })\n                end)\n            )\n\n            assert.equals(\n                \"languages: expected table, got number\",\n                assert.has_error(function()\n                    Pkg:new(spec { languages = 23 })\n                end)\n            )\n        end)\n    end\n\n    it(\"should create new handle\", function()\n        local dummy = registry.get_package \"dummy\"\n        local callback = spy.new()\n        dummy:once(\"install:handle\", callback)\n        local handle = dummy:new_install_handle()\n        assert.spy(callback).was_called(1)\n        assert.spy(callback).was_called_with(match.ref(handle))\n        handle:close()\n    end)\n\n    it(\"should not create new handle if one already exists\", function()\n        local dummy = registry.get_package \"dummy\"\n        dummy.install_handle = mock.new {\n            is_closed = mockx.returns(false),\n        }\n        local handle_handler = spy.new()\n        dummy:once(\"install:handle\", handle_handler)\n        local err = assert.has_error(function()\n            dummy:new_install_handle()\n        end)\n        assert.equals(\"Cannot create new install handle because existing handle is not closed.\", err)\n        assert.spy(handle_handler).was_called(0)\n        dummy.install_handle = nil\n    end)\n\n    it(\"should successfully install package\", function()\n        local dummy = registry.get_package \"dummy\"\n        local package_install_success_handler = spy.new()\n        local package_install_failed_handler = spy.new()\n        local install_success_handler = spy.new()\n        local install_failed_handler = spy.new()\n        registry:once(\"package:install:success\", package_install_success_handler)\n        registry:once(\"package:install:failed\", package_install_failed_handler)\n        dummy:once(\"install:success\", install_success_handler)\n        dummy:once(\"install:failed\", install_failed_handler)\n\n        local handle = dummy:install { version = \"1337\" }\n\n        assert.wait(function()\n            assert.is_true(handle:is_closed())\n            assert.is_true(dummy:is_installed())\n        end)\n\n        assert.wait(function()\n            assert.spy(install_success_handler).was_called(1)\n            assert.spy(install_success_handler).was_called_with(match.instanceof(receipt.InstallReceipt))\n            assert.spy(package_install_success_handler).was_called(1)\n            assert\n                .spy(package_install_success_handler)\n                .was_called_with(match.is_ref(dummy), match.instanceof(receipt.InstallReceipt))\n            assert.spy(package_install_failed_handler).was_called(0)\n            assert.spy(install_failed_handler).was_called(0)\n        end)\n    end)\n\n    it(\"should fail to install package\", function()\n        local dummy = registry.get_package \"dummy\"\n        stub(dummy.spec.source, \"install\", function()\n            error(\"I simply refuse to be installed.\", 0)\n        end)\n        local package_install_success_handler = spy.new()\n        local package_install_failed_handler = spy.new()\n        local install_success_handler = spy.new()\n        local install_failed_handler = spy.new()\n        registry:once(\"package:install:success\", package_install_success_handler)\n        registry:once(\"package:install:failed\", package_install_failed_handler)\n        dummy:once(\"install:success\", install_success_handler)\n        dummy:once(\"install:failed\", install_failed_handler)\n\n        local handle = dummy:install { version = \"1337\" }\n\n        assert.wait(function()\n            assert.is_true(handle:is_closed())\n            assert.is_false(dummy:is_installed())\n        end)\n\n        assert.wait(function()\n            assert.spy(install_failed_handler).was_called(1)\n            assert.spy(install_failed_handler).was_called_with \"I simply refuse to be installed.\"\n            assert.spy(package_install_failed_handler).was_called(1)\n            assert\n                .spy(package_install_failed_handler)\n                .was_called_with(match.is_ref(dummy), \"I simply refuse to be installed.\")\n            assert.spy(package_install_success_handler).was_called(0)\n            assert.spy(install_success_handler).was_called(0)\n        end)\n    end)\n\n    it(\"should be able to start package installation outside of main loop\", function()\n        local dummy = registry.get_package \"dummy\"\n\n        local handle = a.run_blocking(function()\n            -- Move outside the main loop\n            a.wait(function(resolve)\n                local timer = vim.loop.new_timer()\n                timer:start(0, 0, function()\n                    timer:close()\n                    resolve()\n                end)\n            end)\n            assert.is_true(vim.in_fast_event())\n\n            return assert.is_not.has_error(function()\n                return dummy:install()\n            end)\n        end)\n    end)\n\n    it(\"should be able to instantiate package outside of main loop\", function()\n        local dummy = registry.get_package \"registry\"\n\n        -- Move outside the main loop\n        a.run_blocking(function()\n            a.wait(function(resolve)\n                local timer = vim.loop.new_timer()\n                timer:start(0, 0, function()\n                    timer:close()\n                    resolve()\n                end)\n            end)\n\n            assert.is_true(vim.in_fast_event())\n            local pkg = assert.is_not.has_error(function()\n                return Pkg:new(dummy.spec)\n            end)\n            assert.same(dummy.spec, pkg.spec)\n        end)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/path_spec.lua",
    "content": "local path = require \"mason-core.path\"\n\ndescribe(\"path\", function()\n    it(\"concatenates paths\", function()\n        assert.equals(\"foo/bar/baz\", path.concat { \"foo\", \"bar\", \"baz\" })\n        assert.equals(\"foo/bar/baz\", path.concat { \"foo/\", \"bar/\", \"baz/\" })\n    end)\n\n    it(\"identifies subdirectories\", function()\n        assert.is_true(path.is_subdirectory(\"/foo/bar\", \"/foo/bar/baz\"))\n        assert.is_true(path.is_subdirectory(\"/foo/bar\", \"/foo/bar\"))\n        assert.is_false(path.is_subdirectory(\"/foo/bar\", \"/foo/bas/baz\"))\n        assert.is_false(path.is_subdirectory(\"/foo/bar\", \"/foo/bars/baz\"))\n    end)\n\n    describe(\"relative ::\", function()\n        local matrix = {\n            {\n                from = \"/home/user/dir1/fileA\",\n                to = \"/home/user/dir1/fileB\",\n                expected = \"fileB\",\n            },\n            {\n                from = \"/home/user/dir1/fileA\",\n                to = \"/home/user/dir2/fileC\",\n                expected = \"../dir2/fileC\",\n            },\n            {\n                from = \"/home/user/dir1/subdir/fileD\",\n                to = \"/home/user/dir1/fileE\",\n                expected = \"../fileE\",\n            },\n            {\n                from = \"/home/user/dir1/subdir/fileD\",\n                to = \"/home/user/dir1/subdir/fileF\",\n                expected = \"fileF\",\n            },\n            {\n                from = \"/home/user/dir1/fileG\",\n                to = \"/home/user/dir2/subdir/fileH\",\n                expected = \"../dir2/subdir/fileH\",\n            },\n            {\n                from = \"/home/user/dir1/subdir1/subdir2/fileI\",\n                to = \"/home/user/dir1/fileJ\",\n                expected = \"../../fileJ\",\n            },\n            {\n                from = \"/fileK\",\n                to = \"/home/fileL\",\n                expected = \"home/fileL\",\n            },\n            {\n                from = \"/home/user/fileM\",\n                to = \"/home/user/dir1/dir2/fileL\",\n                expected = \"dir1/dir2/fileL\",\n            },\n        }\n\n        for _, test_case in ipairs(matrix) do\n            it((\"should resolve from %s to %s: %s\"):format(test_case.from, test_case.to, test_case.expected), function()\n                assert.equals(test_case.expected, path.relative(test_case.from, test_case.to))\n            end)\n        end\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/pep440_spec.lua",
    "content": "local pep440 = require \"mason-core.pep440\"\n\ndescribe(\"pep440 version checking\", function()\n    it(\"should check single version specifier\", function()\n        assert.is_false(pep440.check_version(\"3.5.0\", \">=3.6\"))\n        assert.is_true(pep440.check_version(\"3.6.0\", \">=3.6\"))\n        assert.is_false(pep440.check_version(\"3.6.0\", \">=3.6.1\"))\n    end)\n\n    it(\"should check version specifier with lower and upper bound\", function()\n        assert.is_true(pep440.check_version(\"3.8.0\", \">=3.8,<3.12\"))\n        assert.is_false(pep440.check_version(\"3.12.0\", \">=3.8,<3.12\"))\n        assert.is_true(pep440.check_version(\"3.12.0\", \">=3.8,<4.0.0\"))\n    end)\n\n    it(\"should check multiple specifiers with different constraints\", function()\n        assert.is_false(pep440.check_version(\"3.5.0\", \"!=4.0,<=4.0,>=3.8\"))\n        assert.is_false(pep440.check_version(\"4.0.0\", \"!=4.0,<=4.0,>=3.8\"))\n        assert.is_true(pep440.check_version(\"3.8.1\", \"!=4.0,<=4.0,>=3.8\"))\n        assert.is_true(pep440.check_version(\"3.12.0\", \"!=4.0,<=4.0,>=3.8\"))\n    end)\n\n    it(\"should support ~= operators\", function()\n        assert.is_true(pep440.check_version(\"3.12.0\", \"~=3.10\"))\n        assert.is_true(pep440.check_version(\"3.10.4\", \"~=3.10.0\"))\n        assert.is_true(pep440.check_version(\"3.12.4\", \"~=3.0\"))\n        assert.is_true(pep440.check_version(\"3.12.4\", \"~=3.12.4\"))\n\n        assert.is_false(pep440.check_version(\"4.0.0\", \"~=3.10\"))\n        assert.is_false(pep440.check_version(\"3.11.0\", \"~=3.10.0\"))\n        assert.is_false(pep440.check_version(\"3.10.0\", \"~=3.10.5\"))\n        assert.is_false(pep440.check_version(\"3.11.0\", \"~=4.0\"))\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/platform_spec.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal _ = require \"mason-core.functional\"\nlocal match = require \"luassert.match\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\n\nlocal spawn = require \"mason-core.spawn\"\n\n---@param contents string\nlocal function stub_etc_os_release(contents)\n    stub(spawn, \"bash\")\n    spawn.bash.on_call_with({ \"-c\", \"cat /etc/*-release\" }).returns(Result.success {\n        stdout = contents,\n    })\nend\n\ndescribe(\"platform\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    local function platform()\n        package.loaded[\"mason-core.platform\"] = nil\n        return require \"mason-core.platform\"\n    end\n\n    local function stub_uname(uname)\n        stub(vim.loop, \"os_uname\")\n        vim.loop.os_uname.returns(uname)\n    end\n\n    ---@param libc '\"glibc\"' | '\"musl\"'\n    local function stub_libc(libc)\n        stub(os, \"execute\")\n        stub(vim.fn, \"executable\")\n        stub(vim.fn, \"system\")\n        vim.fn.executable.on_call_with(\"ldd\").returns(1)\n        vim.fn.executable.on_call_with(\"getconf\").returns(1)\n        if libc == \"musl\" then\n            vim.fn.system.on_call_with({ \"getconf\", \"GNU_LIBC_VERSION\" }).returns \"\"\n            vim.fn.system.on_call_with({ \"ldd\", \"--version\" }).returns \"musl libc (aarch64)\"\n        elseif libc == \"glibc\" then\n            vim.fn.system.on_call_with({ \"getconf\", \"GNU_LIBC_VERSION\" }).returns \"glibc 2.35\"\n            vim.fn.system.on_call_with({ \"ldd\", \"--version\" }).returns \"ldd (Ubuntu GLIBC 2.35-0ubuntu3.1) 2.35\"\n        end\n    end\n\n    local function stub_mac()\n        stub(vim.fn, \"has\")\n        vim.fn.has.on_call_with(\"mac\").returns(1)\n        vim.fn.has.on_call_with(\"unix\").returns(1)\n        vim.fn.has.on_call_with(\"linux\").returns(0)\n        vim.fn.has.on_call_with(match._).returns(0)\n    end\n\n    local function stub_linux()\n        stub(vim.fn, \"has\")\n        vim.fn.has.on_call_with(\"mac\").returns(0)\n        vim.fn.has.on_call_with(\"unix\").returns(1)\n        vim.fn.has.on_call_with(\"linux\").returns(1)\n        vim.fn.has.on_call_with(match._).returns(0)\n    end\n\n    local function stub_windows()\n        stub(vim.fn, \"has\")\n        vim.fn.has.on_call_with(\"win32\").returns(1)\n        vim.fn.has.on_call_with(match._).returns(0)\n    end\n\n    it(\"should be able to detect platform and arch\", function()\n        stub_mac()\n        stub_uname { machine = \"aarch64\" }\n        assert.is_true(platform().is.mac_arm64)\n        assert.is_false(platform().is.mac_x64)\n        assert.is_false(platform().is.nothing)\n    end)\n\n    it(\"should be able to detect macos\", function()\n        stub_mac()\n        assert.is_true(platform().is.mac)\n        assert.is_true(platform().is.darwin)\n        assert.is_true(platform().is.unix)\n        assert.is_false(platform().is.linux)\n        assert.is_false(platform().is.win)\n    end)\n\n    it(\"should be able to detect linux\", function()\n        stub_linux()\n        assert.is_false(platform().is.mac)\n        assert.is_false(platform().is.darwin)\n        assert.is_true(platform().is.unix)\n        assert.is_true(platform().is.linux)\n        assert.is_false(platform().is.win)\n    end)\n\n    it(\"should be able to detect windows\", function()\n        stub_windows()\n        assert.is_false(platform().is.mac)\n        assert.is_false(platform().is.darwin)\n        assert.is_false(platform().is.unix)\n        assert.is_false(platform().is.linux)\n        assert.is_true(platform().is.win)\n    end)\n\n    it(\"should be able to detect correct triple based on libc\", function()\n        stub_linux()\n        stub_uname { machine = \"aarch64\" }\n        stub_libc \"musl\"\n        assert.is_false(platform().is.linux_x64_musl)\n        assert.is_false(platform().is.linux_x64_gnu)\n        assert.is_true(platform().is.linux_arm64_musl)\n        assert.is_false(platform().is.linux_arm64_gnu)\n        assert.is_false(platform().is.linux_arm64_gnu)\n    end)\n\n    it(\"should be able to detect correct triple based on sysname\", function()\n        stub_linux()\n        stub_uname { machine = \"aarch64\", sysname = \"OpenBSD\" }\n        stub_libc \"musl\"\n        assert.is_false(platform().is.linux_x64_musl)\n        assert.is_false(platform().is.linux_x64_gnu)\n        assert.is_false(platform().is.linux_arm64_gnu)\n        assert.is_false(platform().is.linux_arm64_gnu)\n        assert.is_true(platform().is.linux_arm64_openbsd)\n    end)\n\n    it(\"should run correct case on linux\", function()\n        local unix = spy.new()\n        local win = spy.new()\n        local mac = spy.new()\n        local linux = spy.new()\n\n        stub_linux()\n        platform().when {\n            unix = unix,\n            win = win,\n            linux = linux,\n            mac = mac,\n        }\n        assert.spy(unix).was_not_called()\n        assert.spy(mac).was_not_called()\n        assert.spy(win).was_not_called()\n        assert.spy(linux).was_called(1)\n    end)\n\n    it(\"should run correct case on mac\", function()\n        local unix = spy.new()\n        local win = spy.new()\n        local mac = spy.new()\n        local linux = spy.new()\n\n        stub_mac()\n        platform().when {\n            unix = unix,\n            win = win,\n            linux = linux,\n            mac = mac,\n        }\n        assert.spy(unix).was_not_called()\n        assert.spy(mac).was_called(1)\n        assert.spy(win).was_not_called()\n        assert.spy(linux).was_not_called()\n    end)\n\n    it(\"should run correct case on windows\", function()\n        local unix = spy.new()\n        local win = spy.new()\n        local mac = spy.new()\n        local linux = spy.new()\n\n        stub_windows()\n        platform().when {\n            unix = unix,\n            win = win,\n            linux = linux,\n            mac = mac,\n        }\n        assert.spy(unix).was_not_called()\n        assert.spy(mac).was_not_called()\n        assert.spy(win).was_called(1)\n        assert.spy(linux).was_not_called()\n    end)\n\n    it(\"should run correct case on mac (unix)\", function()\n        local unix = spy.new()\n        local win = spy.new()\n\n        stub_mac()\n        platform().when {\n            unix = unix,\n            win = win,\n        }\n        assert.spy(unix).was_called(1)\n        assert.spy(win).was_not_called()\n    end)\n\n    it(\"should run correct case on linux (unix)\", function()\n        local unix = spy.new()\n        local win = spy.new()\n\n        stub_linux()\n        platform().when {\n            unix = unix,\n            win = win,\n        }\n        assert.spy(unix).was_called(1)\n        assert.spy(win).was_not_called()\n    end)\n\n    describe(\"macOS distribution detection\", function()\n        before_each(function()\n            stub_mac()\n        end)\n\n        it(\"detects macOS\", function()\n            assert.same({ id = \"macOS\", version = {} }, platform().os_distribution())\n        end)\n    end)\n\n    describe(\"Windows distribution detection\", function()\n        before_each(function()\n            stub_windows()\n        end)\n\n        it(\"detects Windows\", function()\n            assert.same({ id = \"windows\", version = {} }, platform().os_distribution())\n        end)\n    end)\n\n    describe(\"Linux distribution detection\", function()\n        before_each(function()\n            stub_linux()\n        end)\n\n        it(\"detects Ubuntu\", function()\n            stub_etc_os_release(_.dedent [[\n                NAME=\"Ubuntu\"\n                VERSION=\"20.04.5 LTS (Focal Fossa)\"\n                ID=ubuntu\n                ID_LIKE=debian\n                PRETTY_NAME=\"Ubuntu 20.04.5 LTS\"\n                VERSION_ID=\"20.04\"\n                HOME_URL=\"https://www.ubuntu.com/\"\n                SUPPORT_URL=\"https://help.ubuntu.com/\"\n                BUG_REPORT_URL=\"https://bugs.launchpad.net/ubuntu/\"\n                PRIVACY_POLICY_URL=\"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\"\n                VERSION_CODENAME=focal\n                UBUNTU_CODENAME=focal\n            ]])\n            assert.same(\n                { id = \"ubuntu\", version = { major = 20, minor = 4 }, version_id = \"20.04\" },\n                platform().os_distribution()\n            )\n        end)\n\n        it(\"detects CentOS\", function()\n            stub_etc_os_release(_.dedent [[\n                NAME=\"CentOS Linux\"\n                VERSION=\"7 (Core)\"\n                ID=\"centos\"\n                ID_LIKE=\"rhel fedora\"\n                VERSION_ID=\"7\"\n                PRETTY_NAME=\"CentOS Linux 7 (Core)\"\n                ANSI_COLOR=\"0;31\"\n                CPE_NAME=\"cpe:/o:centos:centos:7\"\n                HOME_URL=\"https://www.centos.org/\"\n                BUG_REPORT_URL=\"https://bugs.centos.org/\"\n\n                CENTOS_MANTISBT_PROJECT=\"CentOS-7\"\n                CENTOS_MANTISBT_PROJECT_VERSION=\"7\"\n                REDHAT_SUPPORT_PRODUCT=\"centos\"\n                REDHAT_SUPPORT_PRODUCT_VERSION=\"7\"\n            ]])\n            assert.same({ id = \"centos\", version = { major = 7 }, version_id = \"7\" }, platform().os_distribution())\n        end)\n\n        it(\"detects generic Linux\", function()\n            stub(spawn, \"bash\")\n            spawn.bash.returns(Result.failure())\n            assert.same({ id = \"linux-generic\", version = {} }, platform().os_distribution())\n        end)\n\n        it(\"detects generic Linux\", function()\n            stub_etc_os_release(_.dedent [[\n                UNKNOWN_ID=here\n            ]])\n            assert.same({ id = \"linux-generic\", version = {} }, platform().os_distribution())\n        end)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/process_spec.lua",
    "content": "local match = require \"luassert.match\"\nlocal process = require \"mason-core.process\"\nlocal spy = require \"luassert.spy\"\n\ndescribe(\"process.spawn\", function()\n    -- Unix only\n    it(\"should spawn command and feed output to sink\", function()\n        local stdio = process.BufferedSink:new()\n        local callback = spy.new()\n        process.spawn(\"env\", {\n            args = {},\n            env = {\n                \"HELLO=world\",\n                \"MY_ENV=var\",\n            },\n            stdio_sink = stdio,\n        }, callback)\n\n        assert.wait(function()\n            assert.spy(callback).was_called(1)\n            assert.spy(callback).was_called_with(true, 0, match.is_number())\n            assert.equals(table.concat(stdio.buffers.stdout, \"\"), \"HELLO=world\\nMY_ENV=var\\n\")\n        end)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/providers/provider_spec.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal spy = require \"luassert.spy\"\n\ndescribe(\"providers\", function()\n    ---@module \"mason-core.providers\"\n    local provider\n    ---@module \"mason.settings\"\n    local settings\n\n    before_each(function()\n        package.loaded[\"mason-core.providers\"] = nil\n        package.loaded[\"mason.settings\"] = nil\n        provider = require \"mason-core.providers\"\n        settings = require \"mason.settings\"\n    end)\n\n    it(\"should run provided providers\", function()\n        package.loaded[\"failing-provider\"] = {\n            github = {\n                get_all_release_versions = spy.new(function()\n                    return Result.failure \"Failed.\"\n                end),\n            },\n        }\n        package.loaded[\"really-failing-provider\"] = {\n            github = {\n                get_all_release_versions = spy.new(function()\n                    error \"Failed.\"\n                end),\n            },\n        }\n        package.loaded[\"successful-provider\"] = {\n            github = {\n                get_all_release_versions = spy.new(function()\n                    return Result.success { \"1.0.0\", \"2.0.0\" }\n                end),\n            },\n        }\n\n        settings.set {\n            providers = { \"failing-provider\", \"really-failing-provider\", \"successful-provider\" },\n        }\n\n        assert.same(\n            Result.success { \"1.0.0\", \"2.0.0\" },\n            provider.github.get_all_release_versions \"sumneko/lua-language-server\"\n        )\n        assert.spy(package.loaded[\"failing-provider\"].github.get_all_release_versions).was_called()\n        assert\n            .spy(package.loaded[\"failing-provider\"].github.get_all_release_versions)\n            .was_called_with \"sumneko/lua-language-server\"\n        assert.spy(package.loaded[\"really-failing-provider\"].github.get_all_release_versions).was_called()\n        assert\n            .spy(package.loaded[\"really-failing-provider\"].github.get_all_release_versions)\n            .was_called_with \"sumneko/lua-language-server\"\n        assert.spy(package.loaded[\"successful-provider\"].github.get_all_release_versions).was_called()\n        assert\n            .spy(package.loaded[\"successful-provider\"].github.get_all_release_versions)\n            .was_called_with \"sumneko/lua-language-server\"\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/purl_spec.lua",
    "content": "local Purl = require \"mason-core.purl\"\nlocal Result = require \"mason-core.result\"\n\ndescribe(\"purl\", function()\n    it(\"should parse well-formed PURLs\", function()\n        assert.same(\n            Result.success {\n                name = \"rust-analyzer\",\n                namespace = \"rust-lang\",\n                qualifiers = {\n                    target = \"linux_x64_gnu\",\n                    download_url = \"https://github.com/rust-lang/rust-analyzer/releases/download/2022-11-28/rust-analyzer-x86_64-unknown-linux-gnu.gz\",\n                },\n                scheme = \"pkg\",\n                type = \"github\",\n                version = \"2022-11-28\",\n                subpath = \"bin/rust-analyzer\",\n            },\n            Purl.parse \"pkg:github/rust-lang/rust-analyzer@2022-11-28?target=linux_x64_gnu&download_url=https://github.com/rust-lang/rust-analyzer/releases/download/2022-11-28/rust-analyzer-x86_64-unknown-linux-gnu.gz#bin/rust-analyzer\"\n        )\n\n        assert.same(\n            Result.success {\n                scheme = \"pkg\",\n                type = \"github\",\n                namespace = \"rust-lang\",\n                name = \"rust-analyzer\",\n                version = \"2025-04-20\",\n                qualifiers = nil,\n                subpath = nil,\n            },\n            Purl.parse \"pkg:github/rust-lang/rust-analyzer@2025-04-20\"\n        )\n\n        assert.same(\n            Result.success {\n                scheme = \"pkg\",\n                type = \"npm\",\n                namespace = nil,\n                name = \"typescript-language-server\",\n                version = \"10.23.1\",\n                qualifiers = nil,\n                subpath = nil,\n            },\n            Purl.parse \"pkg:npm/typescript-language-server@10.23.1\"\n        )\n\n        assert.same(\n            Result.success {\n                scheme = \"pkg\",\n                type = \"pypi\",\n                namespace = nil,\n                name = \"python-language-server\",\n                version = nil,\n                qualifiers = nil,\n                subpath = nil,\n            },\n            Purl.parse \"pkg:pypi/python-language-server\"\n        )\n\n        assert.same(\n            Result.success {\n                name = \"cli\",\n                namespace = \"@angular\",\n                scheme = \"pkg\",\n                type = \"npm\",\n            },\n            Purl.parse \"pkg:npm/%40angular/cli\"\n        )\n    end)\n\n    it(\"should fail to parse invalid PURLs\", function()\n        assert.same(Result.failure \"Malformed purl (invalid scheme).\", Purl.parse \"scam:github/react@18.0.0\")\n    end)\n\n    it(\"should treat percent-encoded components as case insensitive\", function()\n        local purl = {\n            name = \"sonarlint-vscode\",\n            namespace = \"sonarsource\",\n            scheme = \"pkg\",\n            type = \"github\",\n            version = \"3.18.0+70423\" .. string.char(0xab),\n        }\n        assert.same(Result.success(purl), Purl.parse \"pkg:github/SonarSource/sonarlint-vscode@3.18.0%2b70423%ab\")\n        assert.same(Result.success(purl), Purl.parse \"pkg:github/SonarSource/sonarlint-vscode@3.18.0%2B70423%aB\")\n        assert.same(Result.success(purl), Purl.parse \"pkg:github/SonarSource/sonarlint-vscode@3.18.0%2b70423%AB\")\n        assert.same(Result.success(purl), Purl.parse \"pkg:github/SonarSource/sonarlint-vscode@3.18.0%2B70423%Ab\")\n    end)\nend)\n\ndescribe(\"purl test suite ::\", function()\n    local fs = require \"mason-core.fs\"\n    ---@type { description: string, purl: string, type: string?, namespace: string, name: string?, version: string?, is_invalid: boolean, canonical_purl: string }[]\n    local test_fixture = vim.json.decode(fs.sync.read_file \"./tests/fixtures/purl-test-suite-data.json\")\n\n    local function not_vim_nil(val)\n        if val == vim.NIL then\n            return nil\n        else\n            return val\n        end\n    end\n\n    for _, test in ipairs(test_fixture) do\n        it(test.description, function()\n            local result = Purl.parse(test.purl)\n            if test.is_invalid then\n                assert.is_true(result:is_failure())\n            else\n                assert.same(\n                    Result.success {\n                        scheme = \"pkg\",\n                        type = not_vim_nil(test.type),\n                        namespace = not_vim_nil(test.namespace),\n                        name = not_vim_nil(test.name),\n                        version = not_vim_nil(test.version),\n                        qualifiers = not_vim_nil(test.qualifiers),\n                        subpath = not_vim_nil(test.subpath),\n                    },\n                    result\n                )\n\n                assert.equals(test.canonical_purl, Purl.compile(result:get_or_throw()))\n            end\n        end)\n    end\nend)\n"
  },
  {
    "path": "tests/mason-core/receipt_spec.lua",
    "content": "local InstallReceipt = require(\"mason-core.receipt\").InstallReceipt\nlocal fs = require \"mason-core.fs\"\n\nlocal function fixture(file)\n    return vim.json.decode(fs.sync.read_file((\"./tests/fixtures/receipts/%s\"):format(file)))\nend\n\ndescribe(\"receipt ::\", function()\n    it(\"should parse 1.0 structures\", function()\n        local receipt = InstallReceipt:new(fixture \"1.0.json\")\n\n        assert.equals(\"angular-language-server\", receipt:get_name())\n        assert.equals(\"1.0\", receipt:get_schema_version())\n        assert.same({ type = \"npm\", package = \"@angular/language-server\" }, receipt:get_source())\n        assert.same({\n            bin = {\n                ngserver = \"node_modules/.bin/ngserver\",\n            },\n        }, receipt:get_links())\n        assert.is_true(receipt:is_schema_min \"1.0\")\n    end)\n\n    it(\"should parse 1.1 structures\", function()\n        local receipt = InstallReceipt:new(fixture \"1.1.json\")\n\n        assert.equals(\"angular-language-server\", receipt:get_name())\n        assert.equals(\"1.1\", receipt:get_schema_version())\n        assert.same({\n            type = \"registry+v1\",\n            id = \"pkg:npm/%40angular/language-server@16.1.8\",\n\n            source = {\n                extra_packages = { \"typescript@5.1.3\" },\n                version = \"16.1.8\",\n                package = \"@angular/language-server\",\n            },\n        }, receipt:get_source())\n        assert.same({\n            bin = {\n                ngserver = \"node_modules/.bin/ngserver\",\n            },\n            opt = {},\n            share = {},\n        }, receipt:get_links())\n        assert.is_true(receipt:is_schema_min \"1.1\")\n    end)\n\n    it(\"should parse 2.0 structures\", function()\n        local receipt = InstallReceipt:new(fixture \"2.0.json\")\n\n        assert.equals(\"angular-language-server\", receipt:get_name())\n        assert.equals(\"2.0\", receipt:get_schema_version())\n        assert.same({\n            type = \"registry+v1\",\n            id = \"pkg:npm/%40angular/language-server@19.1.0\",\n            raw = {\n                id = \"pkg:npm/%40angular/language-server@19.1.0\",\n                extra_packages = {\n                    \"typescript@5.4.5\",\n                },\n            },\n        }, receipt:get_source())\n        assert.same({\n            bin = {\n                ngserver = \"node_modules/.bin/ngserver\",\n            },\n            opt = {},\n            share = {},\n        }, receipt:get_links())\n        assert.same({\n            name = \"mason-registry\",\n            version = \"2025-05-03-lawful-clave\",\n            checksums = {\n                [\"registry.json\"] = \"4ae083fe8e50d0bea5382be05c7ede8d2def55ff2b6b89dc129b153039d9f2a2\",\n                [\"registry.json.zip\"] = \"2116d5db7676afe7052de329db4dfbf656054d8c35ce12414eb9d58561b2fde9\",\n            },\n            proto = \"github\",\n            namespace = \"mason-org\",\n        }, receipt:get_registry())\n        assert.is_true(receipt:is_schema_min \"2.0\")\n    end)\n\n    it(\"should retrieve purl information\", function()\n        local receipt_1_0 = InstallReceipt:new(fixture \"1.0.json\")\n        local receipt_1_1 = InstallReceipt:new(fixture \"1.1.json\")\n        local receipt_2_0 = InstallReceipt:new(fixture \"2.0.json\")\n\n        assert.is_nil(receipt_1_0:get_installed_purl())\n        assert.equals(\"pkg:npm/%40angular/language-server@16.1.8\", receipt_1_1:get_installed_purl())\n        assert.equals(\"pkg:npm/%40angular/language-server@19.1.0\", receipt_2_0:get_installed_purl())\n    end)\n\n    describe(\"schema versions ::\", function()\n        it(\"should check minimum compatibility\", function()\n            local receipt_1_0 = InstallReceipt:new { schema_version = \"1.0\" }\n            local receipt_1_1 = InstallReceipt:new { schema_version = \"1.1\" }\n            local receipt_2_0 = InstallReceipt:new { schema_version = \"2.0\" }\n\n            assert.is_true(receipt_1_0:is_schema_min \"1.0\")\n            assert.is_true(receipt_1_1:is_schema_min \"1.0\")\n            assert.is_true(receipt_2_0:is_schema_min \"1.0\")\n\n            assert.is_false(receipt_1_0:is_schema_min \"1.1\")\n            assert.is_true(receipt_1_1:is_schema_min \"1.1\")\n            assert.is_true(receipt_2_0:is_schema_min \"1.1\")\n\n            assert.is_false(receipt_1_0:is_schema_min \"1.2\")\n            assert.is_false(receipt_1_1:is_schema_min \"1.2\")\n            assert.is_true(receipt_2_0:is_schema_min \"2.0\")\n        end)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/result_spec.lua",
    "content": "local Optional = require \"mason-core.optional\"\nlocal Result = require \"mason-core.result\"\nlocal a = require \"mason-core.async\"\nlocal match = require \"luassert.match\"\nlocal spy = require \"luassert.spy\"\n\ndescribe(\"Result ::\", function()\n    it(\"should create success\", function()\n        local result = Result.success \"Hello!\"\n        assert.is_true(result:is_success())\n        assert.is_false(result:is_failure())\n        assert.equals(\"Hello!\", result:get_or_nil())\n    end)\n\n    it(\"should create failure\", function()\n        local result = Result.failure \"Hello!\"\n        assert.is_true(result:is_failure())\n        assert.is_false(result:is_success())\n        assert.equals(\"Hello!\", result:err_or_nil())\n    end)\n\n    it(\"should return value on get_or_throw()\", function()\n        local result = Result.success \"Hello!\"\n        local val = result:get_or_throw()\n        assert.equals(\"Hello!\", val)\n    end)\n\n    it(\"should throw failure on get_or_throw()\", function()\n        local result = Result.failure \"Hello!\"\n        local err = assert.has_error(function()\n            result:get_or_throw()\n        end)\n        assert.equals(\"Hello!\", err)\n    end)\n\n    it(\"should map() success\", function()\n        local result = Result.success \"Hello\"\n        local mapped = result:map(function(x)\n            return x .. \" World!\"\n        end)\n        assert.equals(\"Hello World!\", mapped:get_or_nil())\n        assert.is_nil(mapped:err_or_nil())\n    end)\n\n    it(\"should not map() failure\", function()\n        local result = Result.failure \"Hello\"\n        local mapped = result:map(function(x)\n            return x .. \" World!\"\n        end)\n        assert.equals(\"Hello\", mapped:err_or_nil())\n        assert.is_nil(mapped:get_or_nil())\n    end)\n\n    it(\"should raise exceptions in map()\", function()\n        local result = Result.success \"failure\"\n        local err = assert.has_error(function()\n            result:map(function()\n                error \"error\"\n            end)\n        end)\n        assert.equals(\"error\", err)\n    end)\n\n    it(\"should map_catching() success\", function()\n        local result = Result.success \"Hello\"\n        local mapped = result:map_catching(function(x)\n            return x .. \" World!\"\n        end)\n        assert.equals(\"Hello World!\", mapped:get_or_nil())\n        assert.is_nil(mapped:err_or_nil())\n    end)\n\n    it(\"should not map_catching() failure\", function()\n        local result = Result.failure \"Hello\"\n        local mapped = result:map_catching(function(x)\n            return x .. \" World!\"\n        end)\n        assert.equals(\"Hello\", mapped:err_or_nil())\n        assert.is_nil(mapped:get_or_nil())\n    end)\n\n    it(\"should catch errors in map_catching()\", function()\n        local result = Result.success \"value\"\n        local mapped = result:map_catching(function()\n            error \"This is an error\"\n        end)\n        assert.is_false(mapped:is_success())\n        assert.is_true(mapped:is_failure())\n        assert.is_true(match.has_match \"This is an error$\"(mapped:err_or_nil()))\n    end)\n\n    it(\"should recover errors\", function()\n        local result = Result.failure(\"call an ambulance\"):recover(function(err)\n            return err .. \". but not for me!\"\n        end)\n        assert.is_true(result:is_success())\n        assert.equals(\"call an ambulance. but not for me!\", result:get_or_nil())\n    end)\n\n    it(\"should catch errors in recover\", function()\n        local result = Result.failure(\"call an ambulance\"):recover_catching(function(err)\n            error(\"Oh no... \" .. err, 2)\n        end)\n        assert.is_true(result:is_failure())\n        assert.equals(\"Oh no... call an ambulance\", result:err_or_nil())\n    end)\n\n    it(\"should return results in run_catching\", function()\n        local result = Result.run_catching(function()\n            return \"Hello world!\"\n        end)\n        assert.is_true(result:is_success())\n        assert.equals(\"Hello world!\", result:get_or_nil())\n    end)\n\n    it(\"should return failures in run_catching\", function()\n        local result = Result.run_catching(function()\n            error(\"Oh noes\", 2)\n        end)\n        assert.is_true(result:is_failure())\n        assert.equals(\"Oh noes\", result:err_or_nil())\n    end)\n\n    it(\"should run on_failure if failure\", function()\n        local on_success = spy.new()\n        local on_failure = spy.new()\n        local result = Result.failure(\"Oh noes\"):on_failure(on_failure):on_success(on_success)\n        assert.is_true(result:is_failure())\n        assert.equals(\"Oh noes\", result:err_or_nil())\n        assert.spy(on_failure).was_called(1)\n        assert.spy(on_success).was_called(0)\n        assert.spy(on_failure).was_called_with \"Oh noes\"\n    end)\n\n    it(\"should run on_success if success\", function()\n        local on_success = spy.new()\n        local on_failure = spy.new()\n        local result = Result.success(\"Oh noes\"):on_failure(on_failure):on_success(on_success)\n        assert.is_true(result:is_success())\n        assert.equals(\"Oh noes\", result:get_or_nil())\n        assert.spy(on_failure).was_called(0)\n        assert.spy(on_success).was_called(1)\n        assert.spy(on_success).was_called_with \"Oh noes\"\n    end)\n\n    it(\"should convert success variants to non-empty optionals\", function()\n        local opt = Result.success(\"Hello world!\"):ok()\n        assert.is_true(getmetatable(opt) == Optional)\n        assert.equals(\"Hello world!\", opt:get())\n    end)\n\n    it(\"should convert failure variants to empty optionals\", function()\n        local opt = Result.failure(\"Hello world!\"):ok()\n        assert.is_true(getmetatable(opt) == Optional)\n        assert.is_false(opt:is_present())\n    end)\n\n    it(\"should chain successful results\", function()\n        local success = Result.success(\"First\"):and_then(function(value)\n            return Result.success(value .. \" Second\")\n        end)\n        local failure = Result.success(\"Error\"):and_then(Result.failure)\n\n        assert.is_true(success:is_success())\n        assert.equals(\"First Second\", success:get_or_nil())\n        assert.is_true(failure:is_failure())\n        assert.equals(\"Error\", failure:err_or_nil())\n    end)\n\n    it(\"should not chain failed results\", function()\n        local chain = spy.new()\n        local failure = Result.failure(\"Error\"):and_then(chain)\n\n        assert.is_true(failure:is_failure())\n        assert.equals(\"Error\", failure:err_or_nil())\n        assert.spy(chain).was_not_called()\n    end)\n\n    it(\"should chain failed results\", function()\n        local failure = Result.failure(\"First\"):or_else(function(value)\n            return Result.failure(value .. \" Second\")\n        end)\n        local success = Result.failure(\"Error\"):or_else(Result.success)\n\n        assert.is_true(success:is_success())\n        assert.equals(\"Error\", success:get_or_nil())\n        assert.is_true(failure:is_failure())\n        assert.equals(\"First Second\", failure:err_or_nil())\n    end)\n\n    it(\"should not chain successful results\", function()\n        local chain = spy.new()\n        local failure = Result.success(\"Error\"):or_else(chain)\n\n        assert.is_true(failure:is_success())\n        assert.equals(\"Error\", failure:get_or_nil())\n        assert.spy(chain).was_not_called()\n    end)\n\n    it(\"should pcall\", function()\n        assert.same(\n            Result.success \"Great success!\",\n            Result.pcall(function()\n                return \"Great success!\"\n            end)\n        )\n\n        assert.same(\n            Result.failure \"Task failed successfully!\",\n            Result.pcall(function()\n                error(\"Task failed successfully!\", 0)\n            end)\n        )\n    end)\nend)\n\ndescribe(\"Result.try\", function()\n    it(\"should try functions\", function()\n        assert.same(\n            Result.success \"Hello, world!\",\n            Result.try(function(try)\n                local hello = try(Result.success \"Hello, \")\n                local world = try(Result.success \"world!\")\n                return hello .. world\n            end)\n        )\n\n        assert.same(\n            Result.success(),\n            Result.try(function(try)\n                try(Result.success \"Hello, \")\n                try(Result.success \"world!\")\n            end)\n        )\n\n        assert.same(\n            Result.failure \"Trouble, world!\",\n            Result.try(function(try)\n                local trouble = try(Result.success \"Trouble, \")\n                local world = try(Result.success \"world!\")\n                return try(Result.failure(trouble .. world))\n            end)\n        )\n\n        local failure = Result.try(function(try)\n            local err = try(Result.success \"42\")\n            error(err, 0)\n        end)\n        assert.is_true(failure:is_failure())\n        assert.equals(\"42\", failure:err_or_nil())\n    end)\n\n    it(\"should allow calling async functions inside try blocks\", function()\n        assert.same(\n            Result.success \"Hello, world!\",\n            a.run_blocking(function()\n                return Result.try(function(try)\n                    a.sleep(10)\n                    local hello = try(Result.success \"Hello, \")\n                    local world = try(Result.success \"world!\")\n                    return hello .. world\n                end)\n            end)\n        )\n        local failure = a.run_blocking(function()\n            return Result.try(function(try)\n                a.sleep(10)\n                local err = try(Result.success \"42\")\n                error(err)\n            end)\n        end)\n        assert.is_true(failure:is_failure())\n        assert.is_true(match.matches \": 42$\"(failure:err_or_nil()))\n    end)\n\n    it(\"should not unwrap result values in try blocks\", function()\n        assert.same(\n            Result.failure \"Error!\",\n            Result.try(function()\n                return Result.failure \"Error!\"\n            end)\n        )\n\n        assert.same(\n            Result.success \"Success!\",\n            Result.try(function()\n                return Result.success \"Success!\"\n            end)\n        )\n    end)\n\n    it(\"should allow nesting try blocks\", function()\n        assert.same(\n            Result.success \"Hello from the underworld!\",\n            Result.try(function(try)\n                local greeting = try(Result.success \"Hello from the %s!\")\n                return greeting:format(try(Result.try(function(try)\n                    return try(Result.success \"underworld\")\n                end)))\n            end)\n        )\n    end)\n\n    it(\"should allow nesting try blocks in async scope\", function()\n        assert.same(\n            Result.success \"Hello from the underworld!\",\n            a.run_blocking(function()\n                return Result.try(function(try)\n                    a.sleep(10)\n                    local greeting = try(Result.success \"Hello from the %s!\")\n                    a.sleep(10)\n                    return greeting:format(try(Result.try(function(try)\n                        a.sleep(10)\n                        local value = try(Result.success \"underworld\")\n                        a.sleep(10)\n                        return value\n                    end)))\n                end)\n            end)\n        )\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/spawn_spec.lua",
    "content": "local a = require \"mason-core.async\"\nlocal match = require \"luassert.match\"\nlocal platform = require \"mason-core.platform\"\nlocal process = require \"mason-core.process\"\nlocal spawn = require \"mason-core.spawn\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\n\ndescribe(\"async spawn\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    it(\"should spawn commands and return stdout & stderr\", function()\n        local result = a.run_blocking(spawn.env, {\n            env_raw = { \"FOO=bar\" },\n        })\n        assert.is_true(result:is_success())\n        assert.equals(\"FOO=bar\\n\", result:get_or_nil().stdout)\n        assert.equals(\"\", result:get_or_nil().stderr)\n    end)\n\n    it(\"should use provided stdio_sink\", function()\n        local stdout = spy.new()\n        local stdio = process.StdioSink:new {\n            stdout = stdout,\n        }\n        local result = a.run_blocking(spawn.env, {\n            env_raw = { \"FOO=bar\" },\n            stdio_sink = stdio,\n        })\n        assert.is_true(result:is_success())\n        assert.equals(nil, result:get_or_nil())\n        -- Not 100 %guaranteed it's only called once because output is always buffered, but it's extremely likely\n        assert.spy(stdout).was_called(1)\n        assert.spy(stdout).was_called_with \"FOO=bar\\n\"\n    end)\n\n    it(\"should pass command arguments\", function()\n        local result = a.run_blocking(spawn.bash, {\n            \"-c\",\n            'echo \"Hello $VAR\"',\n            env = { VAR = \"world\" },\n        })\n\n        assert.is_true(result:is_success())\n        assert.equals(\"Hello world\\n\", result:get_or_nil().stdout)\n        assert.equals(\"\", result:get_or_nil().stderr)\n    end)\n\n    it(\"should ignore vim.NIL args\", function()\n        spy.on(process, \"spawn\")\n        local result = a.run_blocking(spawn.bash, {\n            vim.NIL,\n            vim.NIL,\n            \"-c\",\n            { vim.NIL, vim.NIL },\n            'echo \"Hello $VAR\"',\n            env = { VAR = \"world\" },\n        })\n\n        assert.is_true(result:is_success())\n        assert.equals(\"Hello world\\n\", result:get_or_nil().stdout)\n        assert.equals(\"\", result:get_or_nil().stderr)\n        assert.spy(process.spawn).was_called(1)\n        assert.spy(process.spawn).was_called_with(\n            \"bash\",\n            match.tbl_containing {\n                stdio_sink = match.instanceof(process.BufferedSink),\n                env = match.list_containing \"VAR=world\",\n                args = match.tbl_containing {\n                    \"-c\",\n                    'echo \"Hello $VAR\"',\n                },\n            },\n            match.is_function()\n        )\n    end)\n\n    it(\"should flatten table args\", function()\n        local result = a.run_blocking(spawn.bash, {\n            { \"-c\", 'echo \"Hello $VAR\"' },\n            env = { VAR = \"world\" },\n        })\n\n        assert.is_true(result:is_success())\n        assert.equals(\"Hello world\\n\", result:get_or_nil().stdout)\n        assert.equals(\"\", result:get_or_nil().stderr)\n    end)\n\n    it(\"should call on_spawn\", function()\n        local on_spawn = spy.new(function(_, stdio)\n            local stdin = stdio[1]\n            stdin:write \"im so piped rn\"\n            stdin:close()\n        end)\n\n        local result = a.run_blocking(spawn.cat, {\n            { \"-\" },\n            on_spawn = on_spawn,\n        })\n\n        assert.spy(on_spawn).was_called(1)\n        assert.spy(on_spawn).was_called_with(match.is_not.is_nil(), match.is_table(), match.is_number())\n        assert.is_true(result:is_success())\n        assert.equals(\"im so piped rn\", result:get_or_nil().stdout)\n    end)\n\n    it(\"should not call on_spawn if spawning fails\", function()\n        local on_spawn = spy.new()\n\n        local result = a.run_blocking(spawn.this_cmd_doesnt_exist, {\n            on_spawn = on_spawn,\n        })\n\n        assert.spy(on_spawn).was_called(0)\n        assert.is_true(result:is_failure())\n    end)\n\n    it(\"should handle failure to spawn process\", function()\n        stub(process, \"spawn\", function(_, _, callback)\n            callback(false)\n        end)\n\n        local result = a.run_blocking(spawn.my_cmd, {})\n        assert.is_true(result:is_failure())\n        assert.is_nil(result:err_or_nil().exit_code)\n    end)\n\n    it(\"should format failure message\", function()\n        stub(process, \"spawn\", function(cmd, opts, callback)\n            opts.stdio_sink:stderr((\"This is an error message for %s!\"):format(cmd))\n            callback(false, 127)\n        end)\n\n        local result = a.run_blocking(spawn.bash, {})\n        assert.is_true(result:is_failure())\n        assert.equals(\n            \"spawn: bash failed with exit code 127 and signal -. This is an error message for bash!\",\n            tostring(result:err_or_nil())\n        )\n    end)\n\n    describe(\"Windows\", function()\n        -- Note: Tests assume they're executed in a Unix environment (e.g. uses Unix path separators in tests).\n\n        before_each(function()\n            platform.is.win = true\n        end)\n\n        after_each(function()\n            platform.is.win = nil\n        end)\n\n        it(\"should use exepath to get absolute path to executable\", function()\n            stub(process, \"spawn\", function(_, _, callback)\n                callback(true, 0, 0)\n            end)\n\n            local result = a.run_blocking(spawn.bash, { \"arg1\" })\n            assert.is_true(result:is_success())\n            assert.spy(process.spawn).was_called(1)\n            assert.spy(process.spawn).was_called_with(\n                vim.fn.exepath \"bash\",\n                match.tbl_containing {\n                    args = match.same { \"arg1\" },\n                },\n                match.is_function()\n            )\n        end)\n\n        it(\"should use exepath if env.PATH is set\", function()\n            stub(process, \"spawn\", function(_, _, callback)\n                callback(true, 0, 0)\n            end)\n\n            local result = a.run_blocking(spawn.bash, { \"arg1\", env = { PATH = \"C:\\\\some\\\\path:\" .. vim.env.PATH } })\n            assert.is_true(result:is_success())\n            assert.spy(process.spawn).was_called(1)\n            assert.spy(process.spawn).was_called_with(\n                vim.fn.exepath \"bash\",\n                match.tbl_containing {\n                    args = match.same { \"arg1\" },\n                    env = match.is_table(),\n                },\n                match.is_function()\n            )\n        end)\n\n        it(\"should use exepath if env_raw.PATH is set\", function()\n            stub(process, \"spawn\", function(_, _, callback)\n                callback(true, 0, 0)\n            end)\n\n            local result = a.run_blocking(spawn.bash, { \"arg1\", env_raw = { \"PATH=C:\\\\some\\\\path:\" .. vim.env.PATH } })\n            assert.is_true(result:is_success())\n            assert.spy(process.spawn).was_called(1)\n            assert.spy(process.spawn).was_called_with(\n                vim.fn.exepath \"bash\",\n                match.tbl_containing {\n                    args = match.same { \"arg1\" },\n                    env = match.is_table(),\n                },\n                match.is_function()\n            )\n        end)\n\n        it(\"should default to provided cmd if exepath returns nothing\", function()\n            stub(process, \"spawn\", function(_, _, callback)\n                callback(true, 0, 0)\n            end)\n\n            local result = a.run_blocking(spawn.bash, { \"arg1\", env_raw = { \"PATH=C:\\\\some\\\\path\" } })\n            assert.is_true(result:is_success())\n            assert.spy(process.spawn).was_called(1)\n            assert.spy(process.spawn).was_called_with(\n                \"bash\",\n                match.tbl_containing {\n                    args = match.same { \"arg1\" },\n                },\n                match.is_function()\n            )\n        end)\n\n        it(\"should use exepath if with_paths is provided\", function()\n            stub(process, \"spawn\", function(_, _, callback)\n                callback(true, 0, 0)\n            end)\n\n            local result = a.run_blocking(spawn.bash, { \"arg1\", with_paths = { \"C:\\\\some\\\\path\" } })\n            assert.is_true(result:is_success())\n            assert.spy(process.spawn).was_called(1)\n            assert.spy(process.spawn).was_called_with(\n                vim.fn.exepath \"bash\",\n                match.tbl_containing {\n                    args = match.same { \"arg1\" },\n                },\n                match.is_function()\n            )\n        end)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-core/terminator_spec.lua",
    "content": "local InstallHandle = require \"mason-core.installer.InstallHandle\"\nlocal _ = require \"mason-core.functional\"\nlocal a = require \"mason-core.async\"\nlocal match = require \"luassert.match\"\nlocal registry = require \"mason-registry\"\nlocal spy = require \"luassert.spy\"\nlocal stub = require \"luassert.stub\"\nlocal terminator = require \"mason-core.terminator\"\n\n-- describe(\"terminator\", function()\n--     local snapshot\n--\n--     before_each(function()\n--         snapshot = assert.snapshot()\n--     end)\n--\n--     after_each(function()\n--         -- wait for scheduled calls to expire\n--         a.run_blocking(a.wait, vim.schedule)\n--         snapshot:revert()\n--     end)\n--\n--     it(\"should terminate all active handles on nvim exit\", function()\n--         spy.on(InstallHandle, \"terminate\")\n--         local dummy = registry.get_package \"dummy\"\n--         local dummy2 = registry.get_package \"dummy2\"\n--         for _, pkg in ipairs { dummy, dummy2 } do\n--             stub(pkg.spec.source, \"install\", function()\n--                 a.sleep(10000)\n--             end)\n--         end\n--\n--         local dummy_handle = dummy:install()\n--         local dummy2_handle = dummy2:install()\n--\n--         assert.wait(function()\n--             assert.spy(dummy.spec.source.install).was_called()\n--             assert.spy(dummy2.spec.source.install).was_called()\n--         end)\n--\n--         terminator.terminate(5000)\n--\n--         assert.spy(InstallHandle.terminate).was_called(2)\n--         assert.spy(InstallHandle.terminate).was_called_with(match.is_ref(dummy_handle))\n--         assert.spy(InstallHandle.terminate).was_called_with(match.is_ref(dummy2_handle))\n--         assert.wait(function()\n--             assert.is_true(dummy_handle:is_closed())\n--             assert.is_true(dummy2_handle:is_closed())\n--         end)\n--     end)\n--\n--     it(\"should print warning messages\", function()\n--         spy.on(vim.api, \"nvim_echo\")\n--         spy.on(vim.api, \"nvim_err_writeln\")\n--         local dummy = registry.get_package \"dummy\"\n--         local dummy2 = registry.get_package \"dummy2\"\n--         for _, pkg in ipairs { dummy, dummy2 } do\n--             stub(pkg.spec.source, \"install\", function()\n--                 a.sleep(10000)\n--             end)\n--         end\n--\n--         local dummy_handle = dummy:install()\n--         local dummy2_handle = dummy2:install()\n--\n--         assert.wait(function()\n--             assert.spy(dummy.spec.source.install).was_called()\n--             assert.spy(dummy2.spec.source.install).was_called()\n--         end)\n--\n--         terminator.terminate(5000)\n--\n--         assert.spy(vim.api.nvim_echo).was_called(1)\n--         assert.spy(vim.api.nvim_echo).was_called_with({\n--             {\n--                 \"[mason.nvim] Neovim is exiting while packages are still installing. Terminating all installations…\",\n--                 \"WarningMsg\",\n--             },\n--         }, true, {})\n--\n--         a.run_blocking(a.wait, vim.schedule)\n--\n--         assert.spy(vim.api.nvim_err_writeln).was_called(1)\n--         assert.spy(vim.api.nvim_err_writeln).was_called_with(_.dedent [[\n--                 [mason.nvim] Neovim exited while the following packages were installing. Installation was aborted.\n--                 - dummy\n--                 - dummy2\n--             ]])\n--         assert.wait(function()\n--             assert.is_true(dummy_handle:is_closed())\n--             assert.is_true(dummy2_handle:is_closed())\n--         end)\n--     end)\n--\n--     it(\"should send SIGTERM and then SIGKILL after grace period\", function()\n--         spy.on(InstallHandle, \"kill\")\n--         local dummy = registry.get_package \"dummy\"\n--         stub(dummy.spec.source, \"install\", function(ctx)\n--             -- your signals have no power here\n--             ctx.spawn.bash { \"-c\", \"function noop { :; }; trap noop SIGTERM; sleep 999999;\" }\n--         end)\n--\n--         local handle = dummy:install()\n--\n--         assert.wait(function()\n--             assert.spy(dummy.spec.source.install).was_called()\n--         end)\n--         terminator.terminate(50)\n--\n--         assert.wait(function()\n--             assert.spy(InstallHandle.kill).was_called(2)\n--             assert.spy(InstallHandle.kill).was_called_with(match.is_ref(handle), 15) -- SIGTERM\n--             assert.spy(InstallHandle.kill).was_called_with(match.is_ref(handle), 9) -- SIGKILL\n--         end)\n--\n--         assert.wait(function()\n--             assert.is_true(handle:is_closed())\n--         end)\n--     end)\n-- end)\n"
  },
  {
    "path": "tests/mason-core/ui_spec.lua",
    "content": "local Ui = require \"mason-core.ui\"\nlocal a = require \"mason-core.async\"\nlocal display = require \"mason-core.ui.display\"\nlocal match = require \"luassert.match\"\nlocal spy = require \"luassert.spy\"\n\ndescribe(\"ui\", function()\n    it(\"produces a correct tree\", function()\n        local function renderer(state)\n            return Ui.CascadingStyleNode({ \"INDENT\" }, {\n                Ui.When(not state.is_active, function()\n                    return Ui.Text {\n                        \"I'm not active\",\n                        \"Another line\",\n                    }\n                end),\n                Ui.When(state.is_active, function()\n                    return Ui.Text {\n                        \"I'm active\",\n                        \"Yet another line\",\n                    }\n                end),\n            })\n        end\n\n        assert.same({\n            children = {\n                {\n                    type = \"HL_TEXT\",\n                    lines = {\n                        { { \"I'm not active\", \"\" } },\n                        { { \"Another line\", \"\" } },\n                    },\n                },\n                {\n                    type = \"NODE\",\n                    children = {},\n                },\n            },\n            styles = { \"INDENT\" },\n            type = \"CASCADING_STYLE\",\n        }, renderer { is_active = false })\n\n        assert.same({\n            children = {\n                {\n                    type = \"NODE\",\n                    children = {},\n                },\n                {\n                    type = \"HL_TEXT\",\n                    lines = {\n                        { { \"I'm active\", \"\" } },\n                        { { \"Yet another line\", \"\" } },\n                    },\n                },\n            },\n            styles = { \"INDENT\" },\n            type = \"CASCADING_STYLE\",\n        }, renderer { is_active = true })\n    end)\n\n    it(\"renders a tree correctly\", function()\n        local render_output = display._render_node(\n            {\n                win_width = 120,\n            },\n            Ui.CascadingStyleNode({ \"INDENT\" }, {\n                Ui.Keybind(\"i\", \"INSTALL_PACKAGE\", { \"sumneko_lua\" }, true),\n                Ui.HlTextNode {\n                    {\n                        { \"Hello World!\", \"MyHighlightGroup\" },\n                    },\n                    {\n                        { \"Another Line\", \"Comment\" },\n                    },\n                },\n                Ui.HlTextNode {\n                    {\n                        { \"Install something idk\", \"Stuff\" },\n                    },\n                },\n                Ui.StickyCursor { id = \"sticky\" },\n                Ui.Keybind(\"<CR>\", \"INSTALL_PACKAGE\", { \"tsserver\" }, false),\n                Ui.DiagnosticsNode {\n                    message = \"yeah this one's outdated\",\n                    severity = vim.diagnostic.severity.WARN,\n                    source = \"trust me bro\",\n                },\n                Ui.Text { \"I'm a text node\" },\n            })\n        )\n\n        assert.same({\n            highlights = {\n                {\n                    col_start = 2,\n                    col_end = 14,\n                    line = 0,\n                    hl_group = \"MyHighlightGroup\",\n                },\n                {\n                    col_start = 2,\n                    col_end = 14,\n                    line = 1,\n                    hl_group = \"Comment\",\n                },\n                {\n                    col_start = 2,\n                    col_end = 23,\n                    line = 2,\n                    hl_group = \"Stuff\",\n                },\n            },\n            lines = { \"  Hello World!\", \"  Another Line\", \"  Install something idk\", \"  I'm a text node\" },\n            virt_texts = {},\n            sticky_cursors = { line_map = { [3] = \"sticky\" }, id_map = { [\"sticky\"] = 3 } },\n            keybinds = {\n                {\n                    effect = \"INSTALL_PACKAGE\",\n                    key = \"i\",\n                    line = -1,\n                    payload = { \"sumneko_lua\" },\n                },\n                {\n                    effect = \"INSTALL_PACKAGE\",\n                    key = \"<CR>\",\n                    line = 3,\n                    payload = { \"tsserver\" },\n                },\n            },\n            diagnostics = {\n                {\n                    line = 3,\n                    message = \"yeah this one's outdated\",\n                    source = \"trust me bro\",\n                    severity = vim.diagnostic.severity.WARN,\n                },\n            },\n        }, render_output)\n    end)\nend)\n\ndescribe(\"integration test\", function()\n    it(\"calls vim APIs as expected during rendering\", function()\n        local window = display.new_view_only_win(\"test\", \"my-filetype\")\n\n        window.view(function(state)\n            return Ui.Node {\n                Ui.Keybind(\"U\", \"EFFECT\", nil, true),\n                Ui.Text {\n                    \"Line number 1!\",\n                    state.text,\n                },\n                Ui.Keybind(\"R\", \"R_EFFECT\", { state.text }),\n                Ui.HlTextNode {\n                    {\n                        { \"My highlighted text\", \"MyHighlightGroup\" },\n                    },\n                },\n            }\n        end)\n\n        local mutate_state = window.state { text = \"Initial state\" }\n\n        local clear_namespace = spy.on(vim.api, \"nvim_buf_clear_namespace\")\n        local buf_set_option = spy.on(vim.api, \"nvim_buf_set_option\")\n        local win_set_option = spy.on(vim.api, \"nvim_win_set_option\")\n        local set_lines = spy.on(vim.api, \"nvim_buf_set_lines\")\n        local set_extmark = spy.on(vim.api, \"nvim_buf_set_extmark\")\n        local add_highlight = spy.on(vim.api, \"nvim_buf_add_highlight\")\n        local set_keymap = spy.on(vim.keymap, \"set\")\n\n        window.init {\n            effects = {\n                [\"EFFECT\"] = function() end,\n                [\"R_EFFECT\"] = function() end,\n            },\n            winhighlight = {\n                \"NormalFloat:MasonNormal\",\n                \"CursorLine:MasonCursorLine\",\n            },\n        }\n        window.open()\n\n        -- Initial window and buffer creation + initial render\n        a.run_blocking(a.wait, vim.schedule)\n\n        assert.spy(win_set_option).was_called(9)\n        assert.spy(win_set_option).was_called_with(match.is_number(), \"number\", false)\n        assert.spy(win_set_option).was_called_with(match.is_number(), \"relativenumber\", false)\n        assert.spy(win_set_option).was_called_with(match.is_number(), \"wrap\", false)\n        assert.spy(win_set_option).was_called_with(match.is_number(), \"spell\", false)\n        assert.spy(win_set_option).was_called_with(match.is_number(), \"foldenable\", false)\n        assert.spy(win_set_option).was_called_with(match.is_number(), \"signcolumn\", \"no\")\n        assert.spy(win_set_option).was_called_with(match.is_number(), \"colorcolumn\", \"\")\n        assert.spy(win_set_option).was_called_with(match.is_number(), \"cursorline\", true)\n        assert\n            .spy(win_set_option)\n            .was_called_with(match.is_number(), \"winhighlight\", \"NormalFloat:MasonNormal,CursorLine:MasonCursorLine\")\n\n        assert.spy(buf_set_option).was_called(10)\n        assert.spy(buf_set_option).was_called_with(match.is_number(), \"modifiable\", false)\n        assert.spy(buf_set_option).was_called_with(match.is_number(), \"swapfile\", false)\n        assert.spy(buf_set_option).was_called_with(match.is_number(), \"textwidth\", 0)\n        assert.spy(buf_set_option).was_called_with(match.is_number(), \"buftype\", \"nofile\")\n        assert.spy(buf_set_option).was_called_with(match.is_number(), \"bufhidden\", \"wipe\")\n        assert.spy(buf_set_option).was_called_with(match.is_number(), \"buflisted\", false)\n        assert.spy(buf_set_option).was_called_with(match.is_number(), \"filetype\", \"my-filetype\")\n        assert.spy(buf_set_option).was_called_with(match.is_number(), \"undolevels\", -1)\n\n        assert.spy(set_lines).was_called(1)\n        assert\n            .spy(set_lines)\n            .was_called_with(match.is_number(), 0, -1, false, { \"Line number 1!\", \"Initial state\", \"My highlighted text\" })\n\n        assert.spy(set_extmark).was_called(0)\n\n        assert.spy(add_highlight).was_called(1)\n        assert.spy(add_highlight).was_called_with(match.is_number(), match.is_number(), \"MyHighlightGroup\", 2, 0, 19)\n\n        assert.spy(set_keymap).was_called(2)\n        assert.spy(set_keymap).was_called_with(\n            \"n\",\n            \"U\",\n            match.is_function(),\n            match.tbl_containing { nowait = true, silent = true, buffer = match.is_number() }\n        )\n        assert.spy(set_keymap).was_called_with(\n            \"n\",\n            \"R\",\n            match.is_function(),\n            match.tbl_containing { nowait = true, silent = true, buffer = match.is_number() }\n        )\n\n        assert.spy(clear_namespace).was_called(1)\n        assert.spy(clear_namespace).was_called_with(match.is_number(), match.is_number(), 0, -1)\n\n        mutate_state(function(state)\n            state.text = \"New state\"\n        end)\n\n        assert.spy(set_lines).was_called(1)\n        a.run_blocking(a.wait, vim.schedule)\n        assert.spy(set_lines).was_called(2)\n\n        assert\n            .spy(set_lines)\n            .was_called_with(match.is_number(), 0, -1, false, { \"Line number 1!\", \"New state\", \"My highlighted text\" })\n    end)\n\n    it(\"anchors to sticky cursor\", function()\n        local window = display.new_view_only_win(\"test\", \"my-filetype\")\n        window.view(function(state)\n            local extra_lines = state.show_extra_lines\n                    and Ui.Text {\n                        \"More\",\n                        \"Lines\",\n                        \"Here\",\n                    }\n                or Ui.Node {}\n            return Ui.Node {\n                extra_lines,\n                Ui.Text {\n                    \"Line 1\",\n                    \"Line 2\",\n                    \"Line 3\",\n                    \"Line 4\",\n                    \"Special line\",\n                },\n                Ui.StickyCursor { id = \"special\" },\n                Ui.Text {\n                    \"Line 6\",\n                    \"Line 7\",\n                    \"Line 8\",\n                    \"Line 9\",\n                    \"Line 10\",\n                },\n            }\n        end)\n\n        local mutate_state = window.state { show_extra_lines = false }\n        window.init {}\n        window.open()\n        a.run_blocking(a.wait, vim.schedule)\n        window.set_cursor { 5, 3 } -- move cursor to sticky line\n        mutate_state(function(state)\n            state.show_extra_lines = true\n        end)\n        a.run_blocking(a.wait, vim.schedule)\n        local cursor = window.get_cursor()\n        assert.same({ 8, 3 }, cursor)\n    end)\n\n    it(\"should respect border ui setting\", function()\n        local nvim_open_win = spy.on(vim.api, \"nvim_open_win\")\n\n        local window = display.new_view_only_win(\"test\", \"my-filetype\")\n        window.view(function()\n            return Ui.Node {}\n        end)\n        window.state {}\n        window.init { border = \"rounded\" }\n        window.open()\n        a.run_blocking(a.wait, vim.schedule)\n\n        assert.spy(nvim_open_win).was_called(1)\n        assert.spy(nvim_open_win).was_called_with(\n            match.is_number(),\n            true,\n            match.tbl_containing {\n                border = \"rounded\",\n            }\n        )\n    end)\n\n    it(\"should not apply cascading styles to empty lines\", function()\n        local render_output = display._render_node(\n            {\n                win_width = 120,\n            },\n            Ui.CascadingStyleNode({ \"INDENT\" }, {\n                Ui.HlTextNode {\n                    {\n                        { \"Hello World!\", \"MyHighlightGroup\" },\n                    },\n                    {\n                        { \"\", \"\" },\n                    },\n                },\n            })\n        )\n\n        assert.same({ \"  Hello World!\", \"\" }, render_output.lines)\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-registry/api_spec.lua",
    "content": "local Result = require \"mason-core.result\"\nlocal match = require \"luassert.match\"\nlocal stub = require \"luassert.stub\"\n\ndescribe(\"mason-registry API\", function()\n    local snapshot\n\n    before_each(function()\n        snapshot = assert.snapshot()\n    end)\n\n    after_each(function()\n        snapshot:revert()\n    end)\n\n    ---@module \"mason-registry.api\"\n    local api\n    local fetch\n    before_each(function()\n        fetch = stub.new()\n        package.loaded[\"mason-core.fetch\"] = fetch\n        package.loaded[\"mason-registry.api\"] = nil\n        api = require \"mason-registry.api\"\n    end)\n\n    it(\"should stringify query parameters\", function()\n        fetch.returns(Result.success [[{}]])\n\n        api.get(\"/api/data\", {\n            params = {\n                page = 2,\n                page_limit = 10,\n                sort = \"ASC\",\n            },\n        })\n\n        assert.spy(fetch).was_called(1)\n        assert.spy(fetch).was_called_with(\"https://api.mason-registry.dev/api/data?page=2&page_limit=10&sort=ASC\", {\n            headers = {\n                Accept = \"application/vnd.mason-registry.v1+json; q=1.0, application/json; q=0.8\",\n            },\n        })\n    end)\n\n    it(\"should deserialize JSON\", function()\n        fetch.returns(Result.success [[{\"field\": [\"value\"]}]])\n\n        local result = api.get(\"/\"):get_or_throw()\n\n        assert.same({ field = { \"value\" } }, result)\n    end)\n\n    it(\"should interpolate path parameters\", function()\n        fetch.returns(Result.success [[{}]])\n\n        local result = api.github.releases.latest { repo = \"myrepo/name\" }\n\n        assert.is_true(result:is_success())\n        assert.spy(fetch).was_called(1)\n        assert.spy(fetch).was_called_with(match.is_match \"/api/github/myrepo/name/releases/latest$\", match.is_table())\n    end)\n\n    it(\"should percent encode path parameters\", function()\n        assert.equals(\"golang.org%2fx%2ftools%2fgopls\", api.encode_uri_component \"golang.org/x/tools/gopls\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-registry/registry_spec.lua",
    "content": "local Pkg = require \"mason-core.package\"\nlocal registry = require \"mason-registry\"\nlocal test_helpers = require \"mason-test.helpers\"\n\ndescribe(\"mason-registry\", function()\n    it(\"should return package\", function()\n        assert.is_true(getmetatable(registry.get_package \"dummy\").__index == Pkg)\n    end)\n\n    it(\"should error when getting non-existent package\", function()\n        local err = assert.has_error(function()\n            registry.get_package \"non-existent\"\n        end)\n        assert.equals([[Cannot find package \"non-existent\".]], err)\n    end)\n\n    it(\"should check whether package exists\", function()\n        assert.is_true(registry.has_package \"dummy\")\n        assert.is_false(registry.has_package \"non-existent\")\n    end)\n\n    it(\"should get all package specs\", function()\n        assert.equals(3, #registry.get_all_package_specs())\n    end)\n\n    it(\"should check if package is installed\", function()\n        local dummy = registry.get_package \"dummy\"\n        -- TODO unflake this in a better way\n        if dummy:is_installed() then\n            test_helpers.sync_uninstall(dummy)\n        end\n        assert.is_false(registry.is_installed \"dummy\")\n        test_helpers.sync_install(dummy)\n        assert.is_true(registry.is_installed \"dummy\")\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-registry/sources/collection_spec.lua",
    "content": "local LazySourceCollection = require \"mason-registry.sources\"\nlocal SynthesizedSource = require \"mason-registry.sources.synthesized\"\n\ndescribe(\"LazySourceCollection\", function()\n    it(\"should dedupe registries on append/prepend\", function()\n        local coll = LazySourceCollection:new()\n\n        coll:append \"github:mason-org/mason-registry\"\n        coll:prepend \"github:mason-org/mason-registry@2025-05-16\"\n        coll:prepend \"github:my-own/registry\"\n        coll:prepend \"lua:registry\"\n        coll:append \"lua:registry\"\n        coll:append \"file:~/registry\"\n        coll:append \"file:$HOME/registry\"\n\n        assert.equals(4, coll:size())\n        assert.same(\"lua:registry\", coll:get(1):get_full_id())\n        assert.same(\"github:my-own/registry\", coll:get(2):get_full_id())\n        assert.same(\"github:mason-org/mason-registry@2025-05-16\", coll:get(3):get_full_id())\n        assert.same(\"file:~/registry\", coll:get(4):get_full_id())\n    end)\n\n    it(\"should fall back to synthesized source\", function()\n        local coll = LazySourceCollection:new()\n\n        for source in coll:iterate() do\n            assert.is_true(getmetatable(source) == SynthesizedSource)\n            return\n        end\n        error \"Did not fall back to synthesized source\"\n    end)\n\n    it(\"should exclude synthesized source\", function()\n        local coll = LazySourceCollection:new()\n\n        for source in coll:iterate { include_synthesized = false } do\n            error \"Should not iterate.\"\n        end\n    end)\nend)\n"
  },
  {
    "path": "tests/mason-registry/sources/lua_spec.lua",
    "content": "local LuaRegistrySource = require \"mason-registry.sources.lua\"\n\ndescribe(\"Lua registry source\", function()\n    it(\"should get package\", function()\n        local source = LuaRegistrySource:new {\n            mod = \"dummy-registry.index\",\n        }\n        assert.is_true(source:install():is_success())\n        assert.is_not_nil(source:get_package \"dummy\")\n        assert.is_nil(source:get_package \"non-existent\")\n    end)\n\n    it(\"should get all package names\", function()\n        local source = LuaRegistrySource:new {\n            mod = \"dummy-registry.index\",\n        }\n        assert.is_true(source:install():is_success())\n        local package_names = source:get_all_package_names()\n        table.sort(package_names)\n        assert.same({\n            \"dummy\",\n            \"dummy2\",\n            \"registry\",\n        }, package_names)\n    end)\n\n    it(\"should check if is installed\", function()\n        local installed_source = LuaRegistrySource:new {\n            mod = \"dummy-registry.index\",\n        }\n        local uninstalled_source = LuaRegistrySource:new {\n            mod = \"non-existent\",\n        }\n\n        assert.is_true(installed_source:install():is_success())\n        assert.is_true(installed_source:is_installed())\n        assert.is_false(uninstalled_source:is_installed())\n    end)\n\n    it(\"should stringify instances\", function()\n        assert.equals(\"LuaRegistrySource(mod=pkg-index)\", tostring(LuaRegistrySource:new { mod = \"pkg-index\" }))\n    end)\nend)\n"
  },
  {
    "path": "tests/minimal_init.vim",
    "content": "\" Avoid neovim/neovim#11362\nset display=lastline\nset directory=\"\"\nset noswapfile\n\nlet $mason = getcwd()\nlet $test_helpers = getcwd() .. \"/tests/helpers\"\nlet $dependencies = getcwd() .. \"/dependencies\"\n\nset rtp^=$mason,$test_helpers\nset packpath=$dependencies\n\npackloadall\n\nlua require(\"luassertx\")\n\nlua <<EOF\nmockx = {\n    just_runs = function() end,\n    returns = function(val)\n        return function()\n            return val\n        end\n    end,\n    throws = function(exception)\n        return function()\n            error(exception, 2)\n        end\n    end,\n}\nEOF\n\nlua <<EOF\nlocal path = require \"mason-core.path\"\n\nrequire(\"mason\").setup {\n    log_level = vim.log.levels.DEBUG,\n    install_root_dir = vim.env.INSTALL_ROOT_DIR or path.concat { vim.loop.cwd(), \"tests\", \"fixtures\", \"mason\"},\n    registries = {\n        \"lua:dummy-registry.index\"\n    }\n}\n\nrequire(\"mason-registry\").refresh()\nEOF\n\nfunction! RunTests() abort\n    lua <<EOF\n    require(\"plenary.test_harness\").test_directory(os.getenv(\"FILE\") or \"./tests\", {\n        minimal_init = vim.fn.getcwd() .. \"/tests/minimal_init.vim\",\n        sequential = true,\n    })\nEOF\nendfunction\n"
  },
  {
    "path": "vim.yml",
    "content": "---\nglobals:\n    jit.os:\n        type: string\n        property: read-only\n    package.config:\n        type: string\n        property: read-only\n    vim:\n        any: true\n    assert.wait:\n        args:\n            - type: function\n            - type: number\n              required: false\n    mockx.throws:\n        args:\n            - type: any\n              required: false\n    mockx.just_runs:\n        args: []\n    mockx.returns:\n        args:\n            - type: any\n\n    describe:\n        args:\n            - type: string\n            - type: function\n    before_each:\n        args:\n            - type: function\n    after_each:\n        args:\n            - type: function\n    it:\n        args:\n            - type: string\n            - type: function\n    assert.equals:\n        args:\n            - type: any\n            - type: any\n    assert.same:\n        args:\n            - type: any\n            - type: any\n    assert.is_true:\n        args:\n            - type: any\n    assert.is_false:\n        args:\n            - type: any\n    assert.has_error:\n        args:\n            - type: function\n    assert.is_nil:\n        args:\n            - type: any\n    assert.is_not_nil:\n        args:\n            - type: any\n    assert.spy:\n        any: true\n    assert.snapshot:\n        args: []\n    assert.is_not.has_error:\n        args:\n            - type: function\n"
  }
]