diff options
| author | William Boman <william@redwill.se> | 2022-07-21 14:15:11 +0200 |
|---|---|---|
| committer | William Boman <william@redwill.se> | 2022-07-22 03:03:23 +0200 |
| commit | 1d459b6d19118b9944d5313e4439cb361d607366 (patch) | |
| tree | d48a07eaa5fddebc75ade8bb1d3861992e0c11a3 | |
| download | mason-lspconfig-1d459b6d19118b9944d5313e4439cb361d607366.tar mason-lspconfig-1d459b6d19118b9944d5313e4439cb361d607366.tar.gz mason-lspconfig-1d459b6d19118b9944d5313e4439cb361d607366.tar.bz2 mason-lspconfig-1d459b6d19118b9944d5313e4439cb361d607366.tar.lz mason-lspconfig-1d459b6d19118b9944d5313e4439cb361d607366.tar.xz mason-lspconfig-1d459b6d19118b9944d5313e4439cb361d607366.tar.zst mason-lspconfig-1d459b6d19118b9944d5313e4439cb361d607366.zip | |
init
55 files changed, 2281 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8fe3efd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace=true +max_line_length = 120 +charset = utf-8 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..2924f6e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: [williamboman] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..70db94b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: Ask a question about mason.nvim or get support + url: https://github.com/williamboman/mason.nvim/discussions/new?category=q-a + about: Ask a question or request support for using mason.nvim diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 0000000..de4fe3a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,32 @@ +name: Feature request +description: Suggest an idea for this project +labels: + - enhancement + +body: + - type: markdown + attributes: + value: | + 👋! This is not an issue template for questions! If you have questions, please refer to https://github.com/williamboman/mason.nvim/discussions/categories/q-a :) + + Before filing an issue, make sure that you meet the minimum requirements mentioned in the README. + + - type: textarea + attributes: + label: Is your feature request related to a problem? Please describe. + validations: + required: true + + - type: textarea + attributes: + label: Describe the solution you'd like + validations: + required: true + + - type: textarea + attributes: + label: Describe potential alternatives you've considered + + - type: textarea + attributes: + label: Additional context diff --git a/.github/ISSUE_TEMPLATE/general_issue.yaml b/.github/ISSUE_TEMPLATE/general_issue.yaml new file mode 100644 index 0000000..c1d122b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/general_issue.yaml @@ -0,0 +1,75 @@ +name: Issue +description: Report an issue with mason-lspconfig.nvim + +body: + - type: markdown + attributes: + value: | + 👋! This is not an issue template for questions! If you have questions, please refer to https://github.com/williamboman/mason.nvim/discussions/categories/q-a :) + + Before filing an issue, make sure that you meet the minimum requirements mentioned in the README. + + - type: textarea + attributes: + label: Problem description + description: A clear and concise description of what the issue is and why you think it's an issue with mason.nvim. + validations: + required: true + + - type: textarea + attributes: + label: "Neovim version (>= 0.7)" + description: "Output of `nvim --version`" + placeholder: | + NVIM v0.7.0-dev + Build type: Release + LuaJIT 2.1.0-beta3 + validations: + required: true + + - type: input + attributes: + label: "Operating system/version" + description: "On Linux and Mac systems: `$ uname -a`" + validations: + required: true + + - type: checkboxes + attributes: + label: I've recently downloaded the latest plugin version of mason.nvim & mason-lspconfig.nvim + options: + - label: "Yes" + + - type: input + attributes: + label: Affected packages + description: If this issue is specific to one or more packages, list them here. If not, write 'all'. + validations: + required: true + + - type: textarea + attributes: + label: Actual behavior + description: A short description of what's happening. + validations: + required: true + + - type: textarea + attributes: + label: Expected behavior + description: A short description of the behavior you expected. + validations: + required: true + + - type: textarea + attributes: + label: Healthcheck output + placeholder: ":checkhealth mason" + render: shell + validations: + required: true + + - type: textarea + attributes: + label: Screenshots + description: If applicable, add screenshots to help explain your problem diff --git a/.github/workflows/autogenerate.yml b/.github/workflows/autogenerate.yml new file mode 100644 index 0000000..b148513 --- /dev/null +++ b/.github/workflows/autogenerate.yml @@ -0,0 +1,42 @@ +name: Autogenerate code + +on: + workflow_dispatch: + schedule: + - cron: "0 10 * * *" + +jobs: + autogenerate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: rhysd/action-setup-vim@v1 + with: + neovim: true + version: v0.7.0 + + - name: make generate + run: make generate + + - name: Create Pull Request + id: cpr + uses: peter-evans/create-pull-request@v4 + with: + token: ${{ secrets.PAT }} + author: "William Botman <william+bot@redwill.se>" + committer: "William Botman <william+bot@redwill.se>" + add-paths: lua/mason-lspconfig + commit-message: "chore: update generated code" + branch: chore/generate + branch-suffix: short-commit-hash + delete-branch: true + labels: automerge + title: "chore: update generated code" + + - name: Enable Pull Request Automerge + if: steps.cpr.outputs.pull-request-operation == 'created' + uses: peter-evans/enable-pull-request-automerge@v2 + with: + token: ${{ secrets.PAT }} + pull-request-number: ${{ steps.cpr.outputs.pull-request-number }} + merge-method: squash diff --git a/.github/workflows/check-generated-code-state.yml b/.github/workflows/check-generated-code-state.yml new file mode 100644 index 0000000..99abfe2 --- /dev/null +++ b/.github/workflows/check-generated-code-state.yml @@ -0,0 +1,29 @@ +name: Check generated code state + +on: + push: + branches: + - "main" + pull_request: + +jobs: + check-generated-code-state: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: rhysd/action-setup-vim@v1 + with: + neovim: true + version: v0.7.0 + + - name: make generate + run: make generate + + - name: Ensure there are no diffs + run: | + git update-index -q --refresh + git diff + git diff-index --exit-code --quiet HEAD -- || { + echo '::error::Generated code is not up to date, run "make generate".'; + exit 1; + } diff --git a/.github/workflows/stylua.yml b/.github/workflows/stylua.yml new file mode 100644 index 0000000..e28130b --- /dev/null +++ b/.github/workflows/stylua.yml @@ -0,0 +1,20 @@ +name: Stylua check + +on: + push: + branches: + - "main" + pull_request: + +jobs: + stylua: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Run Stylua check + uses: JohnnyMorganz/stylua-action@1.0.0 + with: + # token is needed because the action allegedly downloads binary from github releases + token: ${{ secrets.GITHUB_TOKEN }} + # CLI arguments + args: --check . diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..68c2e21 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,19 @@ +name: Tests + +on: + push: + branches: + - "main" + pull_request: + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: rhysd/action-setup-vim@v1 + with: + neovim: true + version: v0.7.0 + - name: Run tests + run: make test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68422d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/dependencies +/tests/fixtures/mason @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5c4a527 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +INSTALL_ROOT_DIR:=$(shell pwd)/tests/fixtures/mason +NVIM_HEADLESS:=nvim --headless --noplugin -u tests/minimal_init.vim + +dependencies: + git clone --depth 1 https://github.com/williamboman/mason.nvim dependencies/pack/vendor/start/mason.nvim + git clone --depth 1 https://github.com/nvim-lua/plenary.nvim dependencies/pack/vendor/start/plenary.nvim + git clone --depth 1 https://github.com/neovim/nvim-lspconfig dependencies/pack/vendor/start/nvim-lspconfig + +.PHONY: clean_dependencies +clean_dependencies: + rm -rf dependencies + +.PHONY: clean_fixtures +clean_fixtures: + rm -rf "${INSTALL_ROOT_DIR}" + +.PHONY: clean +clean: clean_fixtures clean_dependencies + +.PHONY: test +test: clean_fixtures dependencies + INSTALL_ROOT_DIR=${INSTALL_ROOT_DIR} $(NVIM_HEADLESS) -c "call RunTests()" + +.PHONY: generate +generate: dependencies + ./scripts/nvim.sh scripts/lua/mason-scripts/mason-lspconfig/generate.lua + +# vim:noexpandtab diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ed8395 --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +[](https://github.com/williamboman/mason-lspconfig.nvim/actions?query=workflow%3ATests+branch%3Amain+event%3Apush) + + +[](https://github.com/sponsors/williamboman) + +# mason-lspconfig.nvim + +<p align="center"> + <code>mason-lspconfig</code> bridges <a + href="https://github.com/williamboman/mason.nvim"><code>mason.nvim</code></a> with the <a + href="https://github.com/neovim/nvim-lspconfig"><code>lspconfig</code></a> plugin - making it easier to use the both + plugins together. +</p> + +# Table of Contents + +- [Introduction](#introduction) +- [Requirements](#requirements) +- [Installation](#installation) +- [Setup](#setup) +- [Commands](#commands) +- [Configuration](#configuration) + +# Introduction + +`mason-lspconfig.nvim` registers a setup hook with `lspconfig` that ensures servers installed with `mason.nvim` are set +up with the necessary extra configuration. It also provides extra convenience commands such as `:LspInstall`. Lastly, it +also provides convenience APIs that allow you to use mason.nvim as the main control for dynamically setting up installed +servers on an ad-hoc basis. + +It is recommended to use this extension if you use `mason.nvim` and `lspconfig`. + +# Requirements + +- neovim `>= 0.7.0` +- `mason.nvim` +- `lspconfig` + +# Installation + +## [Packer](https://github.com/wbthomason/packer.nvim) + +```lua +use { + { "williamboman/mason.nvim", branch = "alpha" }, + "williamboman/mason-lspconfig.nvim", + "neovim/nvim-lspconfig", +} +``` + +## vim-plug + +```vim +Plug "williamboman/mason.nvim", { 'branch': 'alpha' } +Plug "williamboman/mason-lspconfig.nvim" +Plug "neovim/nvim-lspconfig" +``` + +# Setup + +```lua +require("mason").setup() +require("mason-lspconfig").setup() +``` + +Refer to the [Configuration](#configuration) section for information about which settings are available. + +# Commands + +- `:LspInstall [<server>...]` - installs the provided servers +- `:LspUninstall <server> ...` - uninstalls the provided servers + +# Configuration + +You may optionally configure certain behavior of `mason-lspconfig.nvim` when calling the `.setup()` function. Refer to +the [default configuration](#default-configuration) for a list of all available settings. + +Example: + +```lua +require("mason-lspconfig").setup({ + ensure_installed = { "sumneko_lua", "rust_analyzer" } +}) +``` + +## Default configuration + +```lua +local DEFAULT_SETTINGS = { + -- A list of servers to automatically install if they're not already installed. Example: { "rust_analyzer@nightly", "sumneko_lua" } + -- This setting has no relation with the `automatic_installation` setting. + ensure_installed = {}, + + -- Whether servers that are set up (via lspconfig) should be automatically installed if they're not already installed. + -- This setting has no relation with the `ensure_installed` setting. + -- Can either be: + -- - false: Servers are not automatically installed. + -- - true: All servers set up via lspconfig are automatically installed. + -- - { exclude: string[] }: All servers set up via lspconfig, except the ones provided in the list, are automatically installed. + -- Example: automatic_installation = { exclude = { "rust_analyzer", "solargraph" } } + automatic_installation = false, +} +``` diff --git a/doc/mason-lspconfig.txt b/doc/mason-lspconfig.txt new file mode 100644 index 0000000..20392b3 --- /dev/null +++ b/doc/mason-lspconfig.txt @@ -0,0 +1,206 @@ +*mason-lspconfig* + +Minimum version of neovim: 0.7.0 + +Author: William Boman + +============================================================================== +INTRODUCTION *mason-lspconfig-introduction* + +`mason-lspconfig` bridges `mason.nvim` with the `lspconfig`[1] plugin - making +it easier to use the both plugins together. + +It is recommended to use this extension if you use `lspconfig`. + +`mason-lspconfig.nvim` registers a setup hook with `lspconfig` that ensures +servers installed with `mason.nvim` are set up with the necessary extra +configuration. It also provides extra convenience commands such as +`:LspInstall`. Lastly, it also provides convenience APIs that allow you to use +mason.nvim as the main control for dynamically setting up installed servers on +an ad-hoc basis. + +[1] `lspconfig`: https://github.com/neovim/nvim-lspconfig + +============================================================================== +REQUIREMENTS *mason-lspconfig-requirements* + +`mason-lspconfig` requires `mason.nvim` & `lspconfig` to be installed. Note +that `lspconfig` needs to be available when setting up `mason-lspconfig` - so +if you lazy load either plugin make sure `lspconfig` is not loaded after +`mason-lspconfig`. + +Also, make sure not to set up any servers via `lspconfig` _before_ calling +`mason-lspconfig`'s setup function. + +============================================================================== +QUICK START *mason-lspconfig-quickstart* + +The only thing needed to enable the `mason-lspconfig` plugin is to make sure +to call the `setup()` function: + + require("mason").setup() + require("mason-lspconfig").setup() + +============================================================================== +COMMANDS *mason-lspconfig-commands* + + *:LspInstall* +:LspInstall [<server>...] + +Installs the provided servers. This command only accepts servers that have a +corresponding server configuration in `lspconfig`. + +You may also provide a language, like `:LspInstall typescript`. This will +prompt you with a selection of all available servers for that given language. + +When the command is ran without any arguments, the currently active buffer's +'filetype' will be used to identify relevant servers, and you will be prompted +with a selection of all suggested servers. + + *:LspUninstall* +:LspUninstall <server> ... + +Uninstalls the provided servers. + +============================================================================== +SETTINGS *mason-lspconfig-settings* + +You can configure certain behavior of `mason-lspconfig` when calling the +`.setup()` function. + +Refer to |mason-lspconfig-default-settings| for all available settings. + +Example: + + require("mason-lspconfig").setup({ + ensure_installed = { "rust_analyzer", "tsserver" } + }) + + *mason-lspconfig-default-settings* + + local DEFAULT_SETTINGS = { + -- A list of servers to automatically install if they're not already installed. Example: { "rust-analyzer@nightly", "sumneko_lua" } + -- This setting has no relation with the `automatic_installation` setting. + ensure_installed = {}, + + -- Whether servers that are set up (via lspconfig) should be automatically installed if they're not already installed. + -- This setting has no relation with the `ensure_installed` setting. + -- Can either be: + -- - false: Servers are not automatically installed. + -- - true: All servers set up via lspconfig are automatically installed. + -- - { exclude: string[] }: All servers set up via lspconfig, except the ones provided in the list, are automatically installed. + -- Example: automatic_installation = { exclude = { "rust_analyzer", "solargraph" } } + automatic_installation = false, + } + +============================================================================== +DYNAMIC SERVER SETUP *mason-lspconfig-dynamic-server-setup* + +`mason-lspconfig` provides extra opt-in functionality that allow you to set up +LSP servers installed with `mason.nvim` without having to manually edit your +Neovim configuration for every single server you want to use. This is also +convenient if you want to use `mason.nvim` as the main control mechanism for +which servers to set up. It also makes it possible to use newly installed +servers without having to restart Neovim! Example: + + require("mason").setup() + require("mason-lspconfig").setup() + require("mason-lspconfig").setup_handlers { + -- The first entry (without a key) will be the default handler + -- and will be called for each installed server that doesn't have + -- a dedicated handler. + function (server_name) -- default handler (optional) + require("lspconfig")[server_name].setup {} + end, + -- Next, you can provide targeted overrides for specific servers. + -- For example, a handler override for the `rust_analyzer`: + ["rust_analyzer"] = function () + require("rust-tools").setup {} + end + } + +Refer to |mason-lspconfig.setup_handlers()| for more information. + +============================================================================== +Lua module: mason-lspconfig + + *mason-lspconfig.setup()* +setup({config}) + Sets up mason with the provided {config} (see |mason-lspconfig-settings|). + + *mason-lspconfig.setup_handlers()* +setup_handlers({handlers}) + Advanced feature ~ + This is an advanced, opt-in, feature that require some careful reading + of the documentation. + + The recommended method to set up servers with lspconfig is to do so by + following their guides, see |lspconfig-quickstart|. + + Registers the provided {handlers}, to be called by mason when an installed + server supported by lspconfig is ready to be setup. + + {handlers} is a table where the keys are the name of an lspconfig server, + and the values are the function to be called when that server is ready to + be set up (i.e. is installed). + + You may also pass a default handler that will be called when no dedicated + handler is provided. This is done by providing a function without a key + (see example below). + + Note: ~ + The server names provided as keys are the lspconfig server names, not + mason's package names, so for example instead of "lua-language-server" + it's "sumneko_lua". + + Example: ~ + + require("mason-lspconfig").setup_handlers({ + -- The first entry (without a key) will be the default handler + -- and will be called for each installed server that doesn't have + -- a dedicated handler. + function (server_name) -- default handler (optional) + require("lspconfig")[server_name].setup {} + end, + -- Next, you can provide targeted overrides for specific servers. + ["rust_analyzer"] = function () + require("rust-tools").setup {} + end, + ["sumneko_lua"] = function () + lspconfig.sumneko_lua.setup { + settings = { + Lua = { + diagnostics = { + globals = { "vim" } + } + } + } + } + end, + }) + + See also: ~ + You may achieve similar behaviour by manually looping through the + installed servers (see |mason-lspconfig.get_installed_servers()|) and + setting each one up. + + *mason-lspconfig.get_installed_servers()* +get_installed_servers() + Returns the installed LSP servers supported by lspconfig. + + Note: ~ + The returned strings are the lspconfig server names, not the mason + package names. For example, "sumneko_lua" is returned instead of + "lua-language-server". This is useful if you want to loop through the + table and use its values to directly interact with lspconfig (for + example setting up all installed servers). + + Returns: ~ + string[] + + See also: ~ + |mason-registry.get_installed_packages()| + |mason-registry.get_installed_package_names()| + + + vim:tw=78:ft=help:norl:expandtab:sw=4 diff --git a/lua/mason-lspconfig/api/command.lua b/lua/mason-lspconfig/api/command.lua new file mode 100644 index 0000000..136cd35 --- /dev/null +++ b/lua/mason-lspconfig/api/command.lua @@ -0,0 +1,150 @@ +local a = require "mason-core.async" +local Optional = require "mason-core.optional" +local notify = require "mason-core.notify" +local _ = require "mason-core.functional" + +---@async +---@param user_args string[]: The arguments, as provided by the user. +local function parse_packages_from_user_args(user_args) + local Package = require "mason-core.package" + local server_mapping = require "mason-lspconfig.mappings.server" + local language_mapping = require "mason.mappings.language" + + return _.filter_map(function(server_specifier) + local server_name, version = Package.Parse(server_specifier) + -- 1. first see if the provided arg is an actual lspconfig server name + return Optional + .of_nilable(server_mapping.lspconfig_to_package[server_name]) + -- 2. if not, check if it's a language specifier (e.g., "typescript" or "java") + :or_(function() + return Optional.of_nilable(language_mapping[server_name]):map(function(package_names) + local lsp_package_names = _.filter(function(package_name) + return server_mapping.package_to_lspconfig[package_name] ~= nil + end, package_names) + + if #lsp_package_names == 0 then + return nil + end + + return a.promisify(vim.ui.select)(lsp_package_names, { + prompt = ("Please select which server you want to install for language %q:"):format( + server_name + ), + format_item = function(item) + return server_mapping.package_to_lspconfig[item] + end, + }) + end) + end) + :map(function(package_name) + return { package = package_name, version = version } + end) + :if_not_present(function() + notify(("Could not find LSP server %q."):format(server_name), vim.log.levels.ERROR) + end) + end, user_args) +end + +---@async +local function parse_packages_from_heuristics() + local server_mapping = require "mason-lspconfig.mappings.server" + + -- Prompt user which server they want to install (based on the current filetype) + local current_ft = vim.api.nvim_buf_get_option(vim.api.nvim_get_current_buf(), "filetype") + local filetype_mapping = require "mason-lspconfig.mappings.filetype" + return Optional.of_nilable(filetype_mapping[current_ft]) + :map(function(server_names) + return a.promisify(vim.ui.select)(server_names, { + prompt = ("Please select which server you want to install for filetype %q:"):format(current_ft), + }) + end) + :map(function(server_name) + local package_name = server_mapping.lspconfig_to_package[server_name] + return { { package = package_name, version = nil } } + end) + :or_else_get(function() + notify(("No LSP servers found for filetype %q."):format(current_ft), vim.log.levels.ERROR) + return {} + end) +end + +local parse_packages_to_install = _.cond { + { _.compose(_.gt(0), _.length), parse_packages_from_user_args }, + { _.compose(_.equals(0), _.length), parse_packages_from_heuristics }, + { _.T, _.always {} }, +} + +local LspInstall = a.scope(function(servers) + local packages_to_install = parse_packages_to_install(servers) + if #packages_to_install > 0 then + local registry = require "mason-registry" + _.each(function(target) + local pkg = registry.get_package(target.package) + pkg:install { version = target.version } + end, packages_to_install) + local ui = require "mason.ui" + ui.open() + ui.set_view "LSP" + vim.schedule(function() + ui.set_sticky_cursor "installing-section" + end) + end +end) + +vim.api.nvim_create_user_command("LspInstall", function(opts) + LspInstall(opts.fargs) +end, { + desc = "Install one or more LSP servers.", + nargs = "*", + complete = "custom,v:lua.mason_lspconfig_completion.available_server_completion", +}) + +local function LspUninstall(servers) + require("mason.ui").open() + require("mason.ui").set_view "LSP" + local registry = require "mason-registry" + local server_mapping = require "mason-lspconfig.mappings.server" + for _, server_specifier in ipairs(servers) do + local package_name = server_mapping.lspconfig_to_package[server_specifier] + local pkg = registry.get_package(package_name) + pkg:uninstall() + end +end + +vim.api.nvim_create_user_command("LspUninstall", function(opts) + LspUninstall(opts.fargs) +end, { + desc = "Uninstall one or more LSP servers.", + nargs = "+", + complete = "custom,v:lua.mason_lspconfig_completion.installed_server_completion", +}) + +_G.mason_lspconfig_completion = { + available_server_completion = function() + local registry = require "mason-registry" + local server_mapping = require "mason-lspconfig.mappings.server" + local language_mapping = require "mason.mappings.language" + + local package_names = _.filter_map(function(pkg_name) + return Optional.of_nilable(server_mapping.package_to_lspconfig[pkg_name]) + end, registry.get_all_package_names()) + local completion = + _.compose(_.sort_by(_.identity), _.uniq_by(_.identity), _.concat(_.keys(language_mapping)))(package_names) + return table.concat(completion, "\n") + end, + installed_server_completion = function() + local registry = require "mason-registry" + local server_mapping = require "mason-lspconfig.mappings.server" + + local server_names = _.filter_map(function(pkg_name) + return Optional.of_nilable(server_mapping.package_to_lspconfig[pkg_name]) + end, registry.get_installed_package_names()) + table.sort(server_names) + return table.concat(server_names, "\n") + end, +} + +return { + LspInstall = LspInstall, + LspUninstall = LspUninstall, +} diff --git a/lua/mason-lspconfig/init.lua b/lua/mason-lspconfig/init.lua new file mode 100644 index 0000000..33309c3 --- /dev/null +++ b/lua/mason-lspconfig/init.lua @@ -0,0 +1,174 @@ +local log = require "mason-core.log" +local Package = require "mason-core.package" +local Optional = require "mason-core.optional" +local _ = require "mason-core.functional" +local settings = require "mason-lspconfig.settings" +local server_mapping = require "mason-lspconfig.mappings.server" +local path = require "mason-core.path" +local registry = require "mason-registry" +local notify = require "mason-core.notify" +local platform = require "mason-core.platform" + +local M = {} + +---@param lspconfig_server_name string +local function resolve_package(lspconfig_server_name) + return Optional.of_nilable(server_mapping.lspconfig_to_package[lspconfig_server_name]):map(function(package_name) + local ok, pkg = pcall(registry.get_package, package_name) + if ok then + return pkg + end + end) +end + +---@param lspconfig_server_name string +local function resolve_server_config_factory(lspconfig_server_name) + local ok, server_config = pcall(require, ("mason-lspconfig.server_configurations.%s"):format(lspconfig_server_name)) + if ok then + return Optional.of(server_config) + end + return Optional.empty() +end + +---@param t1 table +---@param t2 table +local function merge_in_place(t1, t2) + for k, v in pairs(t2) do + if type(v) == "table" then + if type(t1[k]) == "table" and not vim.tbl_islist(t1[k]) then + merge_in_place(t1[k], v) + else + t1[k] = v + end + else + t1[k] = v + end + end + return t1 +end + +local memoized_set = _.memoize(_.set_of) + +---@param server_name string +local function should_auto_install(server_name) + if settings.current.automatic_installation == true then + return true + end + if type(settings.current.automatic_installation) == "table" then + return not memoized_set(settings.current.automatic_installation.exclude)[server_name] + end + return false +end + +local function setup_lspconfig_hook() + local util = require "lspconfig.util" + local win_exepath_compat = platform.is.win and require "mason-lspconfig.win-exepath-compat" + + util.on_setup = util.add_hook_before(util.on_setup, function(config) + local pkg_name = server_mapping.lspconfig_to_package[config.name] + if not pkg_name then + return + end + + if registry.is_installed(pkg_name) then + resolve_server_config_factory(config.name):if_present(function(config_factory) + merge_in_place(config, config_factory({ install_dir = path.package_prefix(pkg_name) }, config)) + if win_exepath_compat and win_exepath_compat[config.name] and config.cmd and config.cmd[1] then + local exepath = vim.fn.exepath(config.cmd[1]) + if exepath ~= "" then + config.cmd[1] = exepath + else + log.error("Failed to expand cmd path", config.name, config.cmd) + end + end + end) + elseif should_auto_install(config.name) then + local pkg = registry.get_package(pkg_name) + pkg:install():once("closed", function() + if pkg:is_installed() then + -- reload config + require("lspconfig")[config.name].setup(config) + end + end) + end + end) +end + +local function ensure_installed() + for _, server_identifier in ipairs(settings.current.ensure_installed) do + local server_name, version = Package.Parse(server_identifier) + resolve_package(server_name):if_present( + ---@param pkg Package + function(pkg) + if not pkg:is_installed() then + pkg:install { + version = version, + } + end + end + ) + end +end + +---@param config MasonLspconfigSettings | nil +function M.setup(config) + if config then + settings.set(config) + end + + setup_lspconfig_hook() + ensure_installed() + + require "mason-lspconfig.api.command" +end + +---See `:h mason-lspconfig.setup_handlers()` +---@param handlers table<string, fun(server_name: string)> +function M.setup_handlers(handlers) + local default_handler = Optional.of_nilable(handlers[1]) + + _.each(function(handler) + if type(handler) == "string" and not server_mapping.lspconfig_to_package[handler] then + notify( + ("mason-lspconfig.setup_handlers: Received handler for unknown lspconfig server name: %s."):format( + handler + ), + vim.log.levels.WARN + ) + end + end, _.keys(handlers)) + + ---@param pkg_name string + local function get_server_name(pkg_name) + return Optional.of_nilable(server_mapping.package_to_lspconfig[pkg_name]) + end + + local function call_handler(server_name) + log.fmt_trace("Checking handler for %s", server_name) + Optional.of_nilable(handlers[server_name]):or_(_.always(default_handler)):if_present(function(handler) + log.fmt_trace("Calling handler for %s", server_name) + local ok, err = pcall(handler, server_name) + if not ok then + vim.notify(err, vim.log.levels.ERROR) + end + end) + end + + local installed_servers = _.filter_map(get_server_name, registry.get_installed_package_names()) + _.each(call_handler, installed_servers) + registry:on( + "package:install:success", + vim.schedule_wrap(function(pkg) + get_server_name(pkg.name):if_present(call_handler) + end) + ) +end + +---@return string[] +function M.get_installed_servers() + return _.filter_map(function(pkg_name) + return Optional.of_nilable(server_mapping.package_to_lspconfig[pkg_name]) + end, registry.get_installed_package_names()) +end + +return M diff --git a/lua/mason-lspconfig/mappings/filetype.lua b/lua/mason-lspconfig/mappings/filetype.lua new file mode 100644 index 0000000..fc84277 --- /dev/null +++ b/lua/mason-lspconfig/mappings/filetype.lua @@ -0,0 +1,162 @@ +-- THIS FILE IS GENERATED. DO NOT EDIT MANUALLY. +-- stylua: ignore start +return { + OpenFOAM = { "foam_ls" }, + apexcode = { "apex_ls" }, + arduino = { "arduino_language_server" }, + asm = { "asm_lsp" }, + aspnetcorerazor = { "tailwindcss" }, + astro = { "astro", "tailwindcss" }, + ["astro-markdown"] = { "tailwindcss" }, + awk = { "awk_ls" }, + bean = { "beancount" }, + beancount = { "beancount" }, + bib = { "ltex", "texlab" }, + bicep = { "bicep" }, + blade = { "tailwindcss" }, + bsl = { "bsl_ls" }, + c = { "clangd" }, + clar = { "clarity_lsp" }, + clarity = { "clarity_lsp" }, + clojure = { "clojure_lsp" }, + cmake = { "cmake" }, + cpp = { "clangd" }, + crystal = { "crystalline" }, + cs = { "csharp_ls", "omnisharp" }, + css = { "cssls", "emmet_ls", "stylelint_lsp", "tailwindcss" }, + cucumber = { "cucumber_language_server" }, + cuda = { "clangd" }, + d = { "serve_d" }, + dhall = { "dhall_lsp_server" }, + ["django-html"] = { "tailwindcss" }, + dockerfile = { "dockerls" }, + dot = { "dotls" }, + dune = { "ocamllsp" }, + edge = { "tailwindcss" }, + edn = { "clojure_lsp" }, + eelixir = { "elixirls", "tailwindcss" }, + ejs = { "tailwindcss" }, + elixir = { "elixirls" }, + elm = { "elmls" }, + erb = { "tailwindcss" }, + erlang = { "erlangls" }, + eruby = { "tailwindcss" }, + flux = { "flux_lsp" }, + foam = { "foam_ls" }, + fortran = { "fortls" }, + fsharp = { "fsautocomplete" }, + genie = { "vala_ls" }, + gitcommit = { "ltex" }, + go = { "golangci_lint_ls", "gopls" }, + gohtml = { "tailwindcss" }, + gomod = { "golangci_lint_ls", "gopls" }, + gotmpl = { "gopls" }, + graphql = { "graphql" }, + groovy = { "groovyls" }, + haml = { "tailwindcss" }, + handlebars = { "ember", "tailwindcss" }, + haskell = { "hls" }, + haxe = { "haxe_language_server" }, + hbs = { "tailwindcss" }, + heex = { "elixirls", "tailwindcss" }, + hoon = { "hoon_ls" }, + html = { "angularls", "emmet_ls", "html", "tailwindcss" }, + ["html-eex"] = { "tailwindcss" }, + htmldjango = { "tailwindcss" }, + jade = { "tailwindcss" }, + java = { "jdtls" }, + javascript = { "cssmodules_ls", "denols", "ember", "eslint", "quick_lint_js", "rome", "stylelint_lsp", "tailwindcss", "tsserver" }, + ["javascript.jsx"] = { "denols", "eslint", "tsserver" }, + javascriptreact = { "cssmodules_ls", "denols", "eslint", "graphql", "rome", "stylelint_lsp", "tailwindcss", "tsserver" }, + json = { "jsonls", "rome" }, + jsonc = { "jsonls" }, + jsonnet = { "jsonnet_ls" }, + julia = { "julials" }, + kotlin = { "kotlin_language_server" }, + leaf = { "tailwindcss" }, + less = { "cssls", "stylelint_lsp", "tailwindcss" }, + lhaskell = { "hls" }, + libsonnet = { "jsonnet_ls" }, + liquid = { "tailwindcss", "theme_check" }, + llw = { "lelwel_ls" }, + lua = { "sumneko_lua" }, + markdown = { "grammarly", "ltex", "marksman", "prosemd_lsp", "remark_ls", "tailwindcss", "zk" }, + mdx = { "tailwindcss" }, + ["metamath-zero"] = { "mm0_ls" }, + mustache = { "tailwindcss" }, + mysql = { "sqlls", "sqls" }, + ncl = { "nickel_ls" }, + nickel = { "nickel_ls" }, + nim = { "nimls" }, + nix = { "rnix" }, + njk = { "tailwindcss" }, + nunjucks = { "tailwindcss" }, + objc = { "clangd" }, + objcpp = { "clangd" }, + ocaml = { "ocamllsp" }, + ["ocaml.interface"] = { "ocamllsp" }, + ["ocaml.menhir"] = { "ocamllsp" }, + ["ocaml.ocamllex"] = { "ocamllsp" }, + opencl = { "opencl_ls" }, + org = { "ltex" }, + os = { "bsl_ls" }, + perl = { "perlnavigator" }, + php = { "intelephense", "phpactor", "psalm", "tailwindcss" }, + plaintex = { "ltex" }, + postcss = { "tailwindcss" }, + prisma = { "prismals" }, + ps1 = { "powershell_es" }, + puppet = { "puppet" }, + purescript = { "purescriptls" }, + python = { "jedi_language_server", "pylsp", "pyright", "sourcery" }, + ql = { "codeqlls" }, + r = { "r_language_server" }, + razor = { "tailwindcss" }, + reason = { "ocamllsp", "reason_ls", "tailwindcss" }, + rescript = { "rescriptls", "tailwindcss" }, + rmd = { "r_language_server" }, + rnoweb = { "ltex" }, + robot = { "robotframework_ls" }, + rst = { "esbonio", "ltex" }, + ruby = { "solargraph", "sorbet" }, + rust = { "rust_analyzer" }, + sass = { "tailwindcss" }, + scss = { "cssls", "stylelint_lsp", "tailwindcss" }, + sh = { "bashls" }, + slim = { "tailwindcss" }, + slint = { "slint_lsp" }, + sls = { "salt_ls" }, + solidity = { "solang", "solc" }, + sql = { "sqlls", "sqls" }, + stylus = { "tailwindcss" }, + sugarss = { "stylelint_lsp", "tailwindcss" }, + svelte = { "svelte", "tailwindcss" }, + svg = { "lemminx" }, + systemverilog = { "svlangserver", "svls", "verible" }, + teal = { "teal_ls" }, + terraform = { "terraformls", "tflint" }, + tex = { "ltex", "texlab" }, + toml = { "taplo" }, + twig = { "tailwindcss" }, + typescript = { "angularls", "cssmodules_ls", "denols", "ember", "eslint", "rome", "stylelint_lsp", "tailwindcss", "tsserver" }, + ["typescript.tsx"] = { "angularls", "denols", "eslint", "rome", "tsserver" }, + typescriptreact = { "angularls", "cssmodules_ls", "denols", "eslint", "graphql", "rome", "stylelint_lsp", "tailwindcss", "tsserver" }, + vala = { "vala_ls" }, + vb = { "omnisharp" }, + verilog = { "svlangserver", "svls", "verible" }, + vim = { "vimls" }, + visualforce = { "visualforce_ls" }, + vlang = { "vls" }, + vmasm = { "asm_lsp" }, + vue = { "eslint", "stylelint_lsp", "tailwindcss", "volar", "vuels" }, + wxss = { "stylelint_lsp" }, + xml = { "lemminx" }, + xsd = { "lemminx" }, + xsl = { "lemminx" }, + xslt = { "lemminx" }, + yaml = { "yamlls" }, + ["yaml.ansible"] = { "ansiblels" }, + ["yaml.docker-compose"] = { "yamlls" }, + zig = { "zls" }, + zir = { "zls" } +}
\ No newline at end of file diff --git a/lua/mason-lspconfig/mappings/server.lua b/lua/mason-lspconfig/mappings/server.lua new file mode 100644 index 0000000..939042c --- /dev/null +++ b/lua/mason-lspconfig/mappings/server.lua @@ -0,0 +1,127 @@ +local _ = require "mason-core.functional" + +local M = {} + +---Maps lspconfig server config name to its corresponding package name. +M.lspconfig_to_package = { + ["angularls"] = "angular-language-server", + ["ansiblels"] = "ansible-language-server", + ["apex_ls"] = "apex-language-server", + ["arduino_language_server"] = "arduino-language-server", + ["asm_lsp"] = "asm-lsp", + ["astro"] = "astro-language-server", + ["awk_ls"] = "awk-language-server", + ["bashls"] = "bash-language-server", + ["beancount"] = "beancount-language-server", + ["bicep"] = "bicep-lsp", + ["bsl_ls"] = "bsl-language-server", + ["clangd"] = "clangd", + ["clarity_lsp"] = "clarity-lsp", + ["clojure_lsp"] = "clojure-lsp", + ["cmake"] = "cmake-language-server", + ["codeqlls"] = "codeql", + ["crystalline"] = "crystalline", + ["csharp_ls"] = "csharp-language-server", + ["cssls"] = "css-lsp", + ["cssmodules_ls"] = "cucumber-language-server", + ["cucumber_language_server"] = "cucumber-language-server", + ["denols"] = "deno", + ["dhall_lsp_server"] = "dhall-lsp", + ["diagnosticls"] = "diagnostic-languageserver", + ["dockerls"] = "dockerfile-language-server", + ["dotls"] = "dot-language-server", + ["efm"] = "efm", + ["elixirls"] = "elixir-ls", + ["elmls"] = "elm-language-server", + ["ember"] = "ember-language-server", + ["emmet_ls"] = "emmet-ls", + ["erlangls"] = "erlang-ls", + ["esbonio"] = "esbonio", + ["eslint"] = "eslint-lsp", + ["flux_lsp"] = "flux-lsp", + ["foam_ls"] = "foam-language-server", + ["fortls"] = "fortls", + ["fsautocomplete"] = "fsautocomplete", + ["golangci_lint_ls"] = "golangci-lint-langserver", + ["gopls"] = "gopls", + ["grammarly"] = "grammarly-languageserver", + ["graphql"] = "graphql-language-service-cli", + ["groovyls"] = "groovy-language-server", + ["haxe_language_server"] = "haxe-language-server", + ["hls"] = "haskell-language-server", + ["hoon_ls"] = "hoon-language-server", + ["html"] = "html-lsp", + ["intelephense"] = "intelephense", + ["jdtls"] = "jdtls", + ["jedi_language_server"] = "jedi-language-server", + ["jsonls"] = "json-lsp", + ["jsonnet_ls"] = "jsonnet-language-server", + ["julials"] = "julia-lsp", + ["kotlin_language_server"] = "kotlin-language-server", + ["lelwel_ls"] = "lelwel", + ["lemminx"] = "lemminx", + ["ltex"] = "ltex-ls", + ["marksman"] = "marksman", + ["mm0_ls"] = "metamath-zero-lsp", + ["nickel_ls"] = "nickel-lang-lsp", + ["nimls"] = "nimlsp", + ["ocamllsp"] = "ocaml-lsp", + ["omnisharp"] = "omnisharp", + ["opencl_ls"] = "opencl-language-server", + ["perlnavigator"] = "perlnavigator", + ["phpactor"] = "phpactor", + ["powershell_es"] = "powershell-editor-services", + ["prismals"] = "prisma-language-server", + ["prosemd_lsp"] = "prosemd-lsp", + ["psalm"] = "psalm", + ["puppet"] = "puppet-editor-services", + ["purescriptls"] = "purescript-language-server", + ["pylsp"] = "python-lsp-server", + ["pyright"] = "pyright", + ["quick_lint_js"] = "quick-lint-js", + ["r_language_server"] = "r-languageserver", + ["reason_ls"] = "reason-language-server", + ["remark_ls"] = "remark-language-server", + ["rescriptls"] = "rescript-lsp", + ["rnix"] = "rnix-lsp", + ["robotframework_ls"] = "robotframework-lsp", + ["rome"] = "rome", + ["rust_analyzer"] = "rust-analyzer", + ["salt_ls"] = "salt-lsp", + ["serve_d"] = "serve-d", + ["slint_lsp"] = "slint-lsp", + ["solang"] = "solang", + ["solargraph"] = "solargraph", + ["solc"] = "solidity", + ["sorbet"] = "sorbet", + ["sourcery"] = "sourcery", + ["sqlls"] = "sqlls", + ["sqls"] = "sqls", + ["stylelint_lsp"] = "stylelint-lsp", + ["sumneko_lua"] = "lua-language-server", + ["svelte"] = "svelte-language-server", + ["svlangserver"] = "svlangserver", + ["svls"] = "svls", + ["tailwindcss"] = "tailwindcss-language-server", + ["taplo"] = "taplo", + ["teal_ls"] = "teal-language-server", + ["terraformls"] = "terraform-ls", + ["texlab"] = "texlab", + ["tflint"] = "tflint", + ["theme_check"] = "shopify-theme-check", + ["tsserver"] = "typescript-language-server", + ["vala_ls"] = "vala-language-server", + ["verible"] = "verible", + ["vimls"] = "vim-language-server", + ["visualforce_ls"] = "visualforce-language-server", + ["vls"] = "vls", + ["volar"] = "vue-language-server", + ["vuels"] = "vetur-vls", + ["yamlls"] = "yaml-language-server", + ["zk"] = "zk", + ["zls"] = "zls", +} + +M.package_to_lspconfig = _.invert(M.lspconfig_to_package) + +return M diff --git a/lua/mason-lspconfig/server_configurations/angularls/init.lua b/lua/mason-lspconfig/server_configurations/angularls/init.lua new file mode 100644 index 0000000..813e525 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/angularls/init.lua @@ -0,0 +1,39 @@ +local platform = require "mason-core.platform" +local _ = require "mason-core.functional" +local path = require "mason-core.path" + +---@param install_dir string +return function(install_dir) + local append_node_modules = _.map(function(dir) + return path.concat { dir, "node_modules" } + end) + + local function get_cmd(workspace_dir) + local cmd = { + "ngserver", + "--stdio", + "--tsProbeLocations", + table.concat(append_node_modules { install_dir, workspace_dir }, ","), + "--ngProbeLocations", + table.concat( + append_node_modules { + path.concat { install_dir, "node_modules", "@angular", "language-server" }, + workspace_dir, + }, + "," + ), + } + if platform.is_win then + cmd[1] = vim.fn.exepath(cmd[1]) + end + + return cmd + end + + return { + cmd = get_cmd(vim.loop.cwd()), + on_new_config = function(new_config, root_dir) + new_config.cmd = get_cmd(root_dir) + end, + } +end diff --git a/lua/mason-lspconfig/server_configurations/apex_ls/init.lua b/lua/mason-lspconfig/server_configurations/apex_ls/init.lua new file mode 100644 index 0000000..6251dc7 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/apex_ls/init.lua @@ -0,0 +1,8 @@ +local path = require "mason-core.path" + +---@param install_dir string +return function(install_dir) + return { + apex_jar_path = path.concat { install_dir, "apex-jorje-lsp.jar" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/bicep/init.lua b/lua/mason-lspconfig/server_configurations/bicep/init.lua new file mode 100644 index 0000000..ddc0a2a --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/bicep/init.lua @@ -0,0 +1,5 @@ +return function() + return { + cmd = { "bicep-lsp" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/bsl_ls/init.lua b/lua/mason-lspconfig/server_configurations/bsl_ls/init.lua new file mode 100644 index 0000000..62dfa5c --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/bsl_ls/init.lua @@ -0,0 +1,5 @@ +return function() + return { + cmd = { "bsl-language-server" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/elixirls/init.lua b/lua/mason-lspconfig/server_configurations/elixirls/init.lua new file mode 100644 index 0000000..0c424b1 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/elixirls/init.lua @@ -0,0 +1,5 @@ +return function() + return { + cmd = { "elixir-ls" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/esbonio/init.lua b/lua/mason-lspconfig/server_configurations/esbonio/init.lua new file mode 100644 index 0000000..e9d8d98 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/esbonio/init.lua @@ -0,0 +1,5 @@ +return function() + return { + cmd = { "esbonio" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/groovyls/init.lua b/lua/mason-lspconfig/server_configurations/groovyls/init.lua new file mode 100644 index 0000000..495c759 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/groovyls/init.lua @@ -0,0 +1,3 @@ +return function() + return { cmd = "groovy-language-server" } +end diff --git a/lua/mason-lspconfig/server_configurations/julials/README.md b/lua/mason-lspconfig/server_configurations/julials/README.md new file mode 100644 index 0000000..18e6880 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/julials/README.md @@ -0,0 +1,9 @@ +# julials + +## Configuring the Julia Environment + +The Julia Environment will be identified in the following order: + +1) user configuration (`lspconfig.julials.setup { julia_env_path = "/my/env" }`) +2) if the `Project.toml` & `Manifest.toml` (or `JuliaProject.toml` & `JuliaManifest.toml`) files exists in the current project working directory, the current project working directory is used as the environment +3) the result of `Pkg.Types.Context().env.project_file` diff --git a/lua/mason-lspconfig/server_configurations/julials/init.lua b/lua/mason-lspconfig/server_configurations/julials/init.lua new file mode 100644 index 0000000..6166e35 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/julials/init.lua @@ -0,0 +1,50 @@ +local path = require "mason-core.path" +local platform = require "mason-core.platform" +local fs = require "mason-core.fs" +local _ = require "mason-core.functional" + +---@param install_dir string +return function(install_dir) + return { + on_new_config = function(config, workspace_dir) + local env_path = config.julia_env_path and vim.fn.expand(config.julia_env_path) + if not env_path then + local file_exists = _.compose(fs.sync.file_exists, path.concat, _.concat { workspace_dir }) + if file_exists { "Project.toml" } and file_exists { "Manifest.toml" } then + env_path = workspace_dir + elseif file_exists { "JuliaProject.toml" } and file_exists { "JuliaManifest.toml" } then + env_path = workspace_dir + end + end + + if not env_path then + local ok, env = pcall(vim.fn.system, { + "julia", + "--startup-file=no", + "--history-file=no", + "-e", + "using Pkg; print(dirname(Pkg.Types.Context().env.project_file))", + }) + if ok then + env_path = env + end + end + + config.cmd = { + "julia", + "--startup-file=no", + "--history-file=no", + "--depwarn=no", + ("--project=%s"):format(path.concat { install_dir, "scripts", "environments", "languageserver" }), + path.concat { install_dir, "nvim-lsp.jl" }, + vim.env.JULIA_DEPOT_PATH or "", + path.concat { install_dir, "symbolstorev5" }, + env_path, + } + end, + cmd_env = { + JULIA_DEPOT_PATH = path.concat { install_dir, "lsdepot" }, + JULIA_LOAD_PATH = platform.is.win and ";" or ":", + }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/kotlin_language_server/init.lua b/lua/mason-lspconfig/server_configurations/kotlin_language_server/init.lua new file mode 100644 index 0000000..585797f --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/kotlin_language_server/init.lua @@ -0,0 +1,5 @@ +return function() + return { + cmd = { "kotlin-language-server" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/ltex/init.lua b/lua/mason-lspconfig/server_configurations/ltex/init.lua new file mode 100644 index 0000000..f24d86f --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/ltex/init.lua @@ -0,0 +1,5 @@ +return function() + return { + cmd = { "ltex-ls" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/omnisharp/README.md b/lua/mason-lspconfig/server_configurations/omnisharp/README.md new file mode 100644 index 0000000..7825578 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/omnisharp/README.md @@ -0,0 +1,16 @@ +# omnisharp + +## How to enable Omnisharp Mono + +By default, the `omnisharp` server will use the `dotnet` (NET6) runtime to run the server. +To run the server using the Mono runtime, set the `use_modern_net` setting to `false`, like so: + +__This requires the `omnisharp-mono` package to be installed.__ + +```lua +local lspconfig = require("lspconfig") + +lspconfig.omnisharp.setup { + use_modern_net = false +} +``` diff --git a/lua/mason-lspconfig/server_configurations/omnisharp/init.lua b/lua/mason-lspconfig/server_configurations/omnisharp/init.lua new file mode 100644 index 0000000..274ff18 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/omnisharp/init.lua @@ -0,0 +1,5 @@ +return function(install_dir, config) + return { + cmd = { config.use_modern_net == false and "omnisharp-mono" or "omnisharp" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/perlnavigator/init.lua b/lua/mason-lspconfig/server_configurations/perlnavigator/init.lua new file mode 100644 index 0000000..c376dc7 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/perlnavigator/init.lua @@ -0,0 +1,5 @@ +return function() + return { + cmd = { "perlnavigator", "--stdio" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/powershell_es/init.lua b/lua/mason-lspconfig/server_configurations/powershell_es/init.lua new file mode 100644 index 0000000..d36a580 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/powershell_es/init.lua @@ -0,0 +1,6 @@ +---@param install_dir string +return function(install_dir) + return { + bundle_path = install_dir, + } +end diff --git a/lua/mason-lspconfig/server_configurations/psalm/init.lua b/lua/mason-lspconfig/server_configurations/psalm/init.lua new file mode 100644 index 0000000..8dd3645 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/psalm/init.lua @@ -0,0 +1,5 @@ +return function() + return { + cmd = { "psalm-language-server" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/pylsp/README.md b/lua/mason-lspconfig/server_configurations/pylsp/README.md new file mode 100644 index 0000000..2434bb4 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/pylsp/README.md @@ -0,0 +1,16 @@ +# Pylsp + +## Installing pylsp plugins + +Pylsp has [third party plugins](https://github.com/python-lsp/python-lsp-server#3rd-party-plugins) which are not installed by default. + +In order for these plugins to work with the `pylsp` server managed by this plugin, they need to be installed in the same [virtual environment](https://docs.python.org/3/library/venv.html) as `pylsp`. For these reasons, there's a convenient `:PylspInstall <packages>` command that does this for you, for example: + +```vim +:PylspInstall pyls-flake8 pylsp-mypy pyls-isort +``` + +The `:PylspInstall` command will only be available once the `pylsp` server has been set up. + +**Note that these extra pylsp plugins will not be reinstalled if you update/reinstall the `pylsp` server, you will have to manage +them manually.** diff --git a/lua/mason-lspconfig/server_configurations/pylsp/init.lua b/lua/mason-lspconfig/server_configurations/pylsp/init.lua new file mode 100644 index 0000000..9626fdc --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/pylsp/init.lua @@ -0,0 +1,51 @@ +local a = require "mason-core.async" +local _ = require "mason-core.functional" +local pip3 = require "mason-core.managers.pip3" +local process = require "mason-core.process" +local notify = require "mason-core.notify" +local spawn = require "mason-core.spawn" + +---@param install_dir string +return function(install_dir) + vim.api.nvim_create_user_command( + "PylspInstall", + a.scope(function(opts) + local plugins = opts.fargs + local plugins_str = table.concat(plugins, ", ") + notify(("Installing %s..."):format(plugins_str)) + local result = spawn.pip { + "install", + "-U", + "--disable-pip-version-check", + plugins, + stdio_sink = process.simple_sink(), + with_paths = { pip3.venv_path(install_dir) }, + } + if vim.in_fast_event() then + a.scheduler() + end + result + :on_success(function() + notify(("Successfully installed pylsp plugins %s"):format(plugins_str)) + end) + :on_failure(function() + notify("Failed to install requested pylsp plugins.", vim.log.levels.ERROR) + end) + end), + { + desc = "[mason.nvim] Installs the provided packages in the same venv as pylsp.", + nargs = "+", + complete = _.always { + "pyls-flake8", + "pylsp-mypy", + "pyls-spyder", + "pyls-isort", + "python-lsp-black", + "pyls-memestra", + "pylsp-rope", + }, + } + ) + + return {} +end diff --git a/lua/mason-lspconfig/server_configurations/r_language_server/init.lua b/lua/mason-lspconfig/server_configurations/r_language_server/init.lua new file mode 100644 index 0000000..84b004c --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/r_language_server/init.lua @@ -0,0 +1,5 @@ +return function() + return { + cmd = { "r-languageserver" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/rescriptls/init.lua b/lua/mason-lspconfig/server_configurations/rescriptls/init.lua new file mode 100644 index 0000000..2f55cc3 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/rescriptls/init.lua @@ -0,0 +1,5 @@ +return function() + return { + cmd = { "rescript-lsp", "--stdio" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/solang/init.lua b/lua/mason-lspconfig/server_configurations/solang/init.lua new file mode 100644 index 0000000..2d8dcf7 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/solang/init.lua @@ -0,0 +1,14 @@ +local path = require "mason-core.path" +local process = require "mason-core.process" + +---@param install_dir string +return function(install_dir) + return { + cmd_env = { + PATH = process.extend_path { + path.concat { install_dir, "llvm13.0", "bin" }, + path.concat { install_dir, "llvm12.0", "bin" }, -- kept for backwards compatibility + }, + }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/tflint/README.md b/lua/mason-lspconfig/server_configurations/tflint/README.md new file mode 100644 index 0000000..51298d6 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/tflint/README.md @@ -0,0 +1,14 @@ +# tflint + +## Installing TFLint plugins + +TFLint has [third party plugins](https://github.com/terraform-linters/tflint/blob/master/docs/user-guide/plugins.md) which are not installed by default. + +To install TFLint plugins, there's a convenient `:TFLintInit` command that does this for you. It will use Neovim's +current working directory to locate the plugins to install (according to `tflint --init`): + +``` +:TFLintInit +``` + +The `:TFLintInit` command will only be available once the `tflint` server has been set up. diff --git a/lua/mason-lspconfig/server_configurations/visualforce_ls/init.lua b/lua/mason-lspconfig/server_configurations/visualforce_ls/init.lua new file mode 100644 index 0000000..7d4c0ec --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/visualforce_ls/init.lua @@ -0,0 +1,5 @@ +return function() + return { + cmd = { "visualforce-language-server", "--stdio" }, + } +end diff --git a/lua/mason-lspconfig/server_configurations/volar/init.lua b/lua/mason-lspconfig/server_configurations/volar/init.lua new file mode 100644 index 0000000..e1d98e7 --- /dev/null +++ b/lua/mason-lspconfig/server_configurations/volar/init.lua @@ -0,0 +1,27 @@ +local fs = require "mason-core.fs" +local path = require "mason-core.path" + +---@param install_dir string +return function(install_dir) + ---@param dir string + local function get_tsserverlib_path(dir) + return path.concat { dir, "node_modules", "typescript", "lib", "tsserverlibrary.js" } + end + + ---@param workspace_dir string|nil + local function get_typescript_server_path(workspace_dir) + local local_tsserverlib = workspace_dir ~= nil and get_tsserverlib_path(workspace_dir) + local vendored_tsserverlib = get_tsserverlib_path(install_dir) + if local_tsserverlib and fs.sync.file_exists(local_tsserverlib) then + return local_tsserverlib + else + return vendored_tsserverlib + end + end + + return { + on_new_config = function(new_config, new_install_dir) + new_config.init_options.typescript.serverPath = get_typescript_server_path(new_install_dir) + end, + } +end diff --git a/lua/mason-lspconfig/settings.lua b/lua/mason-lspconfig/settings.lua new file mode 100644 index 0000000..a405f95 --- /dev/null +++ b/lua/mason-lspconfig/settings.lua @@ -0,0 +1,27 @@ +local M = {} + +---@class MasonLspconfigSettings +local DEFAULT_SETTINGS = { + -- A list of servers to automatically install if they're not already installed. Example: { "rust_analyzer@nightly", "sumneko_lua" } + -- This setting has no relation with the `automatic_installation` setting. + ensure_installed = {}, + + -- Whether servers that are set up (via lspconfig) should be automatically installed if they're not already installed. + -- This setting has no relation with the `ensure_installed` setting. + -- Can either be: + -- - false: Servers are not automatically installed. + -- - true: All servers set up via lspconfig are automatically installed. + -- - { exclude: string[] }: All servers set up via lspconfig, except the ones provided in the list, are automatically installed. + -- Example: automatic_installation = { exclude = { "rust_analyzer", "solargraph" } } + automatic_installation = false, +} + +M._DEFAULT_SETTINGS = DEFAULT_SETTINGS +M.current = M._DEFAULT_SETTINGS + +---@param opts MasonLspconfigSettings +function M.set(opts) + M.current = vim.tbl_deep_extend("force", M.current, opts) +end + +return M diff --git a/lua/mason-lspconfig/win-exepath-compat.lua b/lua/mason-lspconfig/win-exepath-compat.lua new file mode 100644 index 0000000..995bb6d --- /dev/null +++ b/lua/mason-lspconfig/win-exepath-compat.lua @@ -0,0 +1,82 @@ +---On Windows, Mason will link executables as .cmd wrapper scripts in Mason's bin/ directory. +---Some utilities, libuv in particular (which neovim's RPC client, among others, uses), have problems finding .cmd +---executables in PATH. +---The following is a table of lspconfig servers whose cmd will need to be expanded with |exepath()| in order to be +---successfully located and started with lspconfig. +return { + ["angularls"] = true, + ["arduino_language_server"] = true, + ["asm_lsp"] = true, + ["beancount"] = true, + ["bicep"] = true, + ["bsl_ls"] = true, + ["ccls"] = true, + ["clangd"] = true, + ["clarity_lsp"] = true, + ["clojure_lsp"] = true, + ["cmake"] = true, + ["codeqlls"] = true, + ["crystalline"] = true, + ["csharp_ls"] = true, + ["cssls"] = true, + ["denols"] = true, + ["dhall_lsp_server"] = true, + ["efm"] = true, + ["elixirls"] = true, + ["esbonio"] = true, + ["flux_lsp"] = true, + ["fortls"] = true, + ["fsautocomplete"] = true, + ["golangci_lint_ls"] = true, + ["gopls"] = true, + ["groovyls"] = true, + ["haxe_language_server"] = true, + ["hls"] = true, + ["jdtls"] = true, + ["jedi_language_server"] = true, + ["jsonnet_ls"] = true, + ["kotlin_language_server"] = true, + ["lelwel_ls"] = true, + ["lemminx"] = true, + ["ltex"] = true, + ["mm0_ls"] = true, + ["nickel_ls"] = true, + ["nimls"] = true, + ["ocamllsp"] = true, + ["omnisharp"] = true, + ["perlnavigator"] = true, + ["phpactor"] = true, + ["prosemd_lsp"] = true, + ["psalm"] = true, + ["puppet"] = true, + ["pylsp"] = true, + ["quick_lint_js"] = true, + ["reason_ls"] = true, + ["rescriptls"] = true, + ["rnix"] = true, + ["robotframework_ls"] = true, + ["rust_analyzer"] = true, + ["salt_ls"] = true, + ["scry"] = true, + ["serve_d"] = true, + ["slint_lsp"] = true, + ["solang"] = true, + ["sorbet"] = true, + ["sourcery"] = true, + ["sqlls"] = true, + ["sqls"] = true, + ["sumneko_lua"] = true, + ["svls"] = true, + ["taplo"] = true, + ["teal_ls"] = true, + ["terraformls"] = true, + ["texlab"] = true, + ["tflint"] = true, + ["theme_check"] = true, + ["vala_ls"] = true, + ["verible"] = true, + ["visualforce_ls"] = true, + ["vls"] = true, + ["zk"] = true, + ["zls"] = true, +} diff --git a/scripts/lua/mason-scripts/mason-lspconfig/generate.lua b/scripts/lua/mason-scripts/mason-lspconfig/generate.lua new file mode 100644 index 0000000..66c2ba6 --- /dev/null +++ b/scripts/lua/mason-scripts/mason-lspconfig/generate.lua @@ -0,0 +1,54 @@ +local a = require "mason-core.async" +local path = require "mason-core.path" +local _ = require "mason-core.functional" +local lspconfig_server_mapping = require "mason-lspconfig.mappings.server" +local script_utils = require "mason-scripts.utils" + +local MASON_LSPCONFIG_DIR = path.concat { vim.loop.cwd(), "lua", "mason-lspconfig" } + +local function get_lspconfig(name) + return require(("lspconfig.server_configurations.%s"):format(name)) +end + +---@async +local function create_lspconfig_filetype_map() + local filetype_map = {} + + for _, server_name in ipairs(_.keys(lspconfig_server_mapping.lspconfig_to_package)) do + local config = get_lspconfig(server_name) + for _, filetype in ipairs(config.default_config.filetypes or {}) do + if not filetype_map[filetype] then + filetype_map[filetype] = {} + end + table.insert(filetype_map[filetype], server_name) + table.sort(filetype_map[filetype]) + end + end + + script_utils.write_file( + path.concat { MASON_LSPCONFIG_DIR, "mappings", "filetype.lua" }, + "return " .. vim.inspect(filetype_map), + "w" + ) +end + +---@async +local function ensure_valid_mapping() + local server_mappings = require "mason-lspconfig.mappings.server" + local registry = require "mason-registry" + + for lspconfig_server, mason_package in pairs(server_mappings.lspconfig_to_package) do + local lspconfig_ok, server_config = + pcall(require, ("lspconfig.server_configurations.%s"):format(lspconfig_server)) + local mason_ok, pkg = pcall(registry.get_package, mason_package) + assert(lspconfig_ok and server_config ~= nil, lspconfig_server .. " is not a valid lspconfig server name.") + assert(mason_ok and pkg ~= nil, mason_package .. " is not a valid Mason package name.") + end +end + +a.run_blocking(function() + a.wait_all { + create_lspconfig_filetype_map, + ensure_valid_mapping, + } +end) diff --git a/scripts/lua/mason-scripts/utils.lua b/scripts/lua/mason-scripts/utils.lua new file mode 100644 index 0000000..771d419 --- /dev/null +++ b/scripts/lua/mason-scripts/utils.lua @@ -0,0 +1,21 @@ +local fs = require "mason-core.fs" + +local M = {} + +---@async +---@param path string +---@param contents string +---@param flags string +function M.write_file(path, contents, flags) + fs.async.write_file( + path, + table.concat({ + "-- THIS FILE IS GENERATED. DO NOT EDIT MANUALLY.", + "-- stylua: ignore start", + contents, + }, "\n"), + flags + ) +end + +return M diff --git a/scripts/nvim.sh b/scripts/nvim.sh new file mode 100755 index 0000000..c2dcb6b --- /dev/null +++ b/scripts/nvim.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -exuo pipefail + +declare -x DEPENDENCIES="${PWD}/dependencies" +declare -x MASON_DIR="$PWD" +declare -x MASON_SCRIPT_DIR="${PWD}/scripts" + +nvim -u NONE -E -R --headless \ + --cmd "set rtp^=${MASON_SCRIPT_DIR},${MASON_DIR}" \ + --cmd "set packpath^=${DEPENDENCIES}" \ + --cmd "packloadall" \ + --cmd "luafile $1" \ + --cmd "q" diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 0000000..eb74155 --- /dev/null +++ b/stylua.toml @@ -0,0 +1,2 @@ +indent_type = "Spaces" +call_parentheses = "None" diff --git a/tests/helpers/lua/dummy2_package.lua b/tests/helpers/lua/dummy2_package.lua new file mode 100644 index 0000000..424e47d --- /dev/null +++ b/tests/helpers/lua/dummy2_package.lua @@ -0,0 +1,14 @@ +local Pkg = require "mason-core.package" + +return Pkg.new { + name = "dummy2", + desc = [[This is a dummy2 package.]], + categories = { Pkg.Cat.LSP }, + languages = { Pkg.Lang.Dummy2Lang }, + homepage = "https://example.com", + ---@async + ---@param ctx InstallContext + install = function(ctx) + ctx.receipt:with_primary_source { type = "dummy2" } + end, +} diff --git a/tests/helpers/lua/dummy_package.lua b/tests/helpers/lua/dummy_package.lua new file mode 100644 index 0000000..b38d1cd --- /dev/null +++ b/tests/helpers/lua/dummy_package.lua @@ -0,0 +1,14 @@ +local Pkg = require "mason-core.package" + +return Pkg.new { + name = "dummy", + desc = [[This is a dummy package.]], + categories = { Pkg.Cat.LSP }, + languages = { Pkg.Lang.DummyLang }, + homepage = "https://example.com", + ---@async + ---@param ctx InstallContext + install = function(ctx) + ctx.receipt:with_primary_source { type = "dummy" } + end, +} diff --git a/tests/helpers/lua/luassertx.lua b/tests/helpers/lua/luassertx.lua new file mode 100644 index 0000000..55ea0d7 --- /dev/null +++ b/tests/helpers/lua/luassertx.lua @@ -0,0 +1,70 @@ +local assert = require "luassert" +local match = require "luassert.match" +local a = require "mason-core.async" + +local function wait_for(_, arguments) + ---@type (fun()): Function to execute until it does not error. + local assertions_fn = arguments[1] + ---@type number: Timeout in milliseconds. Defaults to 5000. + local timeout = arguments[2] + timeout = timeout or 15000 + + local start = vim.loop.hrtime() + local is_ok, err + repeat + is_ok, err = pcall(assertions_fn) + if not is_ok then + a.sleep(math.min(timeout, 100)) + end + until is_ok or ((vim.loop.hrtime() - start) / 1e6) > timeout + + if not is_ok then + error(err) + end + + return is_ok +end + +local function tbl_containing(_, arguments, _) + return function(value) + local expected = arguments[1] + for key, val in pairs(expected) do + if match.is_matcher(val) then + if not val(value[key]) then + return false + end + elseif value[key] ~= val then + return false + end + end + return true + end +end + +local function list_containing(_, arguments, _) + return function(value) + local expected = arguments[1] + for _, val in pairs(value) do + if match.is_matcher(expected) then + if expected(val) then + return true + end + elseif expected == val then + return true + end + end + return false + end +end + +local function instanceof(_, arguments, _) + return function(value) + local expected_mt = arguments[1] + return getmetatable(value) == expected_mt + end +end + +assert:register("matcher", "tbl_containing", tbl_containing) +assert:register("matcher", "list_containing", list_containing) +assert:register("matcher", "instanceof", instanceof) +assert:register("assertion", "wait_for", wait_for) diff --git a/tests/helpers/lua/test_helpers.lua b/tests/helpers/lua/test_helpers.lua new file mode 100644 index 0000000..57c0b4f --- /dev/null +++ b/tests/helpers/lua/test_helpers.lua @@ -0,0 +1,49 @@ +---@diagnostic disable: lowercase-global +local util = require "luassert.util" +local spy = require "luassert.spy" + +local a = require "mason-core.async" +local InstallHandle = require "mason-core.installer.handle" +local InstallContext = require "mason-core.installer.context" +local registry = require "mason-registry" + +function async_test(suspend_fn) + return function() + local ok, err = pcall(a.run_blocking, suspend_fn) + if not ok then + error(err, util.errorlevel()) + end + end +end + +mockx = { + just_runs = function() end, + returns = function(val) + return function() + return val + end + end, + throws = function(exception) + return function() + error(exception, 2) + end + end, +} + +---@param package_name string +function InstallHandleGenerator(package_name) + return InstallHandle.new(registry.get_package(package_name)) +end + +---@param handle InstallHandle +---@param opts InstallContextOpts | nil +function InstallContextGenerator(handle, opts) + local context = InstallContext.new(handle, opts or {}) + context.spawn = setmetatable({}, { + __index = function(s, cmd) + s[cmd] = spy.new(mockx.just_runs()) + return s[cmd] + end, + }) + return context +end diff --git a/tests/mason-lspconfig/api/command_spec.lua b/tests/mason-lspconfig/api/command_spec.lua new file mode 100644 index 0000000..0551c77 --- /dev/null +++ b/tests/mason-lspconfig/api/command_spec.lua @@ -0,0 +1,71 @@ +local spy = require "luassert.spy" +local stub = require "luassert.stub" +local match = require "luassert.match" + +local server_mappings = require "mason-lspconfig.mappings.server" +local filetype_mappings = require "mason-lspconfig.mappings.filetype" +local api = require "mason-lspconfig.api.command" +local registry = require "mason-registry" +local Pkg = require "mason-core.package" + +describe(":LspInstall", function() + server_mappings.lspconfig_to_package["dummylsp"] = "dummy" + server_mappings.package_to_lspconfig["dummy"] = "dummylsp" + filetype_mappings.dummylang = { "dummylsp" } + + it("should install the provided servers", function() + local dummy = registry.get_package "dummy" + spy.on(Pkg, "install") + api.LspInstall { "dummylsp@1.0.0" } + assert.spy(Pkg.install).was_called(1) + assert.spy(Pkg.install).was_called_with(match.ref(dummy), { + version = "1.0.0", + }) + end) + + it( + "should prompt user for server to install based on filetype", + async_test(function() + local dummy = registry.get_package "dummy" + spy.on(Pkg, "install") + stub(vim.ui, "select") + vim.ui.select.invokes(function(items, opts, callback) + callback "dummylsp" + end) + vim.cmd [[new | setf dummylang]] + api.LspInstall {} + assert.spy(Pkg.install).was_called(1) + assert.spy(Pkg.install).was_called_with(match.ref(dummy), { + version = nil, + }) + assert.spy(vim.ui.select).was_called(1) + assert.spy(vim.ui.select).was_called_with({ "dummylsp" }, match.is_table(), match.is_function()) + end) + ) + + it( + "should not prompt user for server to install if no filetype match exists", + async_test(function() + spy.on(Pkg, "install") + stub(vim.ui, "select") + vim.cmd [[new | setf nolsplang]] + api.LspInstall {} + assert.spy(Pkg.install).was_called(0) + assert.spy(vim.ui.select).was_called(0) + end) + ) +end) + +describe(":LspUninstall", function() + server_mappings.lspconfig_to_package["dummylsp"] = "dummy" + server_mappings.package_to_lspconfig["dummy"] = "dummylsp" + filetype_mappings.dummylang = { "dummylsp" } + + it("should uninstall the provided servers", function() + local dummy = registry.get_package "dummy" + spy.on(Pkg, "uninstall") + api.LspUninstall { "dummylsp" } + assert.spy(Pkg.uninstall).was_called(1) + assert.spy(Pkg.uninstall).was_called_with(match.ref(dummy)) + end) +end) diff --git a/tests/mason-lspconfig/setup_spec.lua b/tests/mason-lspconfig/setup_spec.lua new file mode 100644 index 0000000..9b960c0 --- /dev/null +++ b/tests/mason-lspconfig/setup_spec.lua @@ -0,0 +1,141 @@ +local match = require "luassert.match" +local stub = require "luassert.stub" +local spy = require "luassert.spy" + +local Pkg = require "mason-core.package" +local mason_lspconfig = require "mason-lspconfig" +local server_mappings = require "mason-lspconfig.mappings.server" +local registry = require "mason-registry" +local filetype_mappings = require "mason-lspconfig.mappings.filetype" + +describe("mason-lspconfig setup", function() + before_each(function() + server_mappings.lspconfig_to_package["dummylsp"] = "dummy" + server_mappings.lspconfig_to_package["dummy2lsp"] = "dummy2" + server_mappings.package_to_lspconfig["dummy"] = "dummylsp" + server_mappings.package_to_lspconfig["dummy2"] = "dummy2lsp" + filetype_mappings.dummylang = { "dummylsp", "dummy2lsp" } + require("lspconfig.util").on_setup = nil + local settings = require "mason-lspconfig.settings" + settings.set(settings._DEFAULT_SETTINGS) + vim.fn.delete(vim.env.INSTALL_ROOT_DIR, "rf") + end) + + it("should set up user commands", function() + mason_lspconfig.setup() + local user_commands = vim.api.nvim_get_commands {} + + assert.is_true(match.tbl_containing { + bang = false, + bar = false, + nargs = "*", + complete = "custom", + definition = "Install one or more LSP servers.", + }(user_commands["LspInstall"])) + + assert.is_true(match.tbl_containing { + bang = false, + bar = false, + definition = "Uninstall one or more LSP servers.", + nargs = "+", + complete = "custom", + }(user_commands["LspUninstall"])) + end) + + it( + "should install servers listed in ensure_installed", + async_test(function() + local dummy = registry.get_package "dummy" + spy.on(Pkg, "install") + + mason_lspconfig.setup { ensure_installed = { "dummylsp@1.0.0" } } + + assert.spy(Pkg.install).was_called(1) + assert.spy(Pkg.install).was_called_with(match.ref(dummy), { + version = "1.0.0", + }) + assert.wait_for(function() + assert.is_true(dummy.handle:is_closed()) + end) + end) + ) + + it( + "should automatically install servers", + async_test(function() + local dummy = registry.get_package "dummy" + local dummy2 = registry.get_package "dummy2" + spy.on(Pkg, "install") + + mason_lspconfig.setup { automatic_installation = true } + local lspconfig = require "lspconfig" + spy.on(lspconfig.dummylsp, "setup") + spy.on(lspconfig.dummy2lsp, "setup") + lspconfig.dummylsp.setup {} + lspconfig.dummy2lsp.setup {} + + assert.spy(Pkg.install).was_called(2) + assert.spy(Pkg.install).was_called_with(match.ref(dummy)) + assert.spy(Pkg.install).was_called_with(match.ref(dummy2)) + + assert.wait_for(function() + assert.is_true(dummy.handle:is_closed()) + assert.is_true(dummy2.handle:is_closed()) + assert.spy(lspconfig.dummylsp.setup).was_called(2) + assert.spy(lspconfig.dummy2lsp.setup).was_called(2) + end) + end) + ) +end) + +describe("mason-lspconfig setup_handlers", function() + before_each(function() + server_mappings.lspconfig_to_package["dummylsp"] = "dummy" + server_mappings.lspconfig_to_package["dummy2lsp"] = "dummy2" + server_mappings.package_to_lspconfig["dummy"] = "dummylsp" + server_mappings.package_to_lspconfig["dummy2"] = "dummy2lsp" + filetype_mappings.dummylang = { "dummylsp", "dummy2lsp" } + require("lspconfig.util").on_setup = nil + local settings = require "mason-lspconfig.settings" + settings.set(settings._DEFAULT_SETTINGS) + end) + + it("should call default handler", function() + stub(registry, "get_installed_package_names") + registry.get_installed_package_names.returns { "dummy" } + local default_handler = spy.new() + + mason_lspconfig.setup_handlers { default_handler } + + assert.spy(default_handler).was_called(1) + assert.spy(default_handler).was_called_with "dummylsp" + end) + + it("should call dedicated handler", function() + stub(registry, "get_installed_package_names") + registry.get_installed_package_names.returns { "dummy" } + local dedicated_handler = spy.new() + local default_handler = spy.new() + + mason_lspconfig.setup_handlers { + default_handler, + ["dummylsp"] = dedicated_handler, + } + + assert.spy(default_handler).was_called(0) + assert.spy(dedicated_handler).was_called(1) + assert.spy(dedicated_handler).was_called_with "dummylsp" + end) + + it("should print warning if registering handler for non-existent server name", function() + spy.on(vim, "notify") + mason_lspconfig.setup_handlers { + doesnt_exist_server = spy.new(), + } + assert.spy(vim.notify).was_called(1) + assert.spy(vim.notify).was_called_with( + "[mason.nvim] mason-lspconfig.setup_handlers: Received handler for unknown lspconfig server name: doesnt_exist_server.", + vim.log.levels.WARN + ) + end) +end) diff --git a/tests/minimal_init.vim b/tests/minimal_init.vim new file mode 100644 index 0000000..c3ffb58 --- /dev/null +++ b/tests/minimal_init.vim @@ -0,0 +1,39 @@ +" Avoid neovim/neovim#11362 +set display=lastline +set directory="" +set noswapfile + +let $mason = getcwd() +let $test_helpers = getcwd() .. "/tests/helpers" +let $dependencies = getcwd() .. "/dependencies" + +set rtp^=$mason,$test_helpers +set packpath=$dependencies + +packloadall + +lua require("luassertx") +lua require("test_helpers") + +lua <<EOF +local index = require "mason-registry.index" +index["dummy"] = "dummy_package" +index["dummy2"] = "dummy2_package" + +local configs = require 'lspconfig.configs' +configs.dummylsp = { default_config = {} } +configs.dummy2lsp = { default_config = {} } + +require("mason").setup { + install_root_dir = vim.env.INSTALL_ROOT_DIR, +} +EOF + +function! RunTests() abort + lua <<EOF + require("plenary.test_harness").test_directory(os.getenv("FILE") or "./tests", { + minimal_init = vim.fn.getcwd() .. "/tests/minimal_init.vim", + sequential = true, + }) +EOF +endfunction |
