From a162fda7f703b40175dfbdf51f7f0ed9a5a92379 Mon Sep 17 00:00:00 2001 From: Erik Reinert <4638629+erikreinert@users.noreply.github.com> Date: Mon, 15 Jul 2024 23:20:48 -0700 Subject: [PATCH] feat: update flake and integration testing (#110) --- .envrc | 1 + .github/workflows/integration.yml | 62 ++++++ .github/workflows/test.yml | 28 --- .gitignore | 2 + .luacheckrc | 20 ++ flake.lock | 39 +++- flake.nix | 92 ++++---- justfile | 19 ++ lua/lspcontainers/init.lua | 345 +++++++++++++++--------------- scripts/test.lua | 30 --- scripts/test.sh | 13 -- test/language_servers_spec.lua | 33 +++ 12 files changed, 398 insertions(+), 286 deletions(-) create mode 100644 .envrc create mode 100644 .github/workflows/integration.yml delete mode 100644 .github/workflows/test.yml create mode 100644 .luacheckrc create mode 100644 justfile delete mode 100644 scripts/test.lua delete mode 100755 scripts/test.sh create mode 100644 test/language_servers_spec.lua diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 0000000..36fd629 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,62 @@ +name: integration + +on: + push: + branches: + - main + pull_request: + +env: + CACHIX_BINARY_CACHE: altf4llc-os + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: cachix/install-nix-action@v27 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v14 + with: + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + name: ${{ env.CACHIX_BINARY_CACHE }} + - uses: actions/checkout@v4 + - run: nix develop -c just check + + build: + needs: check + runs-on: ubuntu-latest + strategy: + matrix: + profile: + - default + - neovim + steps: + - uses: cachix/install-nix-action@v27 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v14 + with: + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + name: ${{ env.CACHIX_BINARY_CACHE }} + - uses: actions/checkout@v4 + - run: nix develop -c just build "${{ matrix.profile }}" + + test: + needs: build + runs-on: ubuntu-latest + strategy: + matrix: + suite: + - lint + - unit + steps: + - uses: cachix/install-nix-action@v27 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v14 + with: + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + name: ${{ env.CACHIX_BINARY_CACHE }} + - uses: actions/checkout@v4 + - run: nix develop -c just test-${{ matrix.suite }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 20542cb..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Tests - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - tests: - runs-on: [ubuntu-latest] - steps: - - uses: actions/checkout@v2 - - # This block is copied from https://github.com/neovim/nvim-lspconfig, great work! - - run: date +%F > todays-date - - name: Restore cache for today's neovim nightly. - uses: actions/cache@v2 - with: - path: _neovim - key: ${{ runner.os }}-nightly-${{ hashFiles('todays-date') }} - - name: Setup from neovim nightly and run test suite - run: | - curl -OL https://raw.githubusercontent.com/norcalli/bot-ci/master/scripts/github-actions-setup.sh - source github-actions-setup.sh nightly-x64 - ./scripts/test.sh diff --git a/.gitignore b/.gitignore index 6503821..3a54a3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.direnv + # Compiled Lua sources luac.out diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..b14d1a8 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,20 @@ +-- vim: ft=lua tw=80 + +-- Rerun tests only if their modification time changed. +cache = true + +ignore = { + "122", -- Setting a read-only field of a global variable. + "212", -- Unused argument, In the case of callback function, _arg_name is easier to understand than _, so this option is set to off. + "631", -- max_line_length, vscode pkg URL is too long +} + +-- Global objects defined in the plugin +globals = { + "Config" +} + +-- Global objects defined by the C code +read_globals = { + "vim", +} diff --git a/flake.lock b/flake.lock index de1cfd3..c538249 100644 --- a/flake.lock +++ b/flake.lock @@ -1,23 +1,54 @@ { "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1719994518, + "narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1666424192, - "narHash": "sha256-rb/a7Kg9s31jqkvdOQHFrUc5ig5kB+O2ZKB8mjU2kW8=", + "lastModified": 1720957393, + "narHash": "sha256-oedh2RwpjEa+TNxhg5Je9Ch6d3W1NKi7DbRO1ziHemA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "4f8287f3d597c73b0d706cfad028c2d51821f64d", + "rev": "693bc46d169f5af9c992095736e82c3488bf7dbb", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixpkgs-unstable", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1719876945, + "narHash": "sha256-Fm2rDDs86sHy0/1jxTOKB1118Q0O3Uc7EC0iXvXKpbI=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/5daf0514482af3f97abaefc78a6606365c9108e2.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/5daf0514482af3f97abaefc78a6606365c9108e2.tar.gz" + } + }, "root": { "inputs": { + "flake-parts": "flake-parts", "nixpkgs": "nixpkgs" } } diff --git a/flake.nix b/flake.nix index c702952..5aae6d9 100644 --- a/flake.nix +++ b/flake.nix @@ -1,50 +1,64 @@ { - inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; }; + description = "lspcontainers.nvim"; - outputs = { self, nixpkgs }: - let - supportedSystems = - [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; - forAllSystems = nixpkgs.lib.genAttrs supportedSystems; - pkgs = forAllSystems (system: - import nixpkgs { - overlays = [ ]; - inherit system; - }); - in { - packages = forAllSystems (system: { - default = pkgs.${system}.vimUtils.buildVimPluginFrom2Nix { - name = "lspcontainers.nvim"; - src = ./.; - }; - neovim = pkgs.${system}.neovim.override { - configure = { - customRC = '' - lua << EOF - ${(builtins.readFile ./init.lua)}; - EOF - ''; - packages.main = with pkgs.${system}.vimPlugins; { - start = [ - nvim-lspconfig - self.packages.${system}.default - ]; + inputs = { + flake-parts.url = "github:hercules-ci/flake-parts"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = inputs @ {flake-parts, ...}: + flake-parts.lib.mkFlake {inherit inputs;} { + systems = ["x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin"]; + + perSystem = { + config, + pkgs, + system, + ... + }: let + inherit (pkgs) just luajitPackages mkShell neovim vimPlugins vimUtils writeShellApplication; + inherit (luajitPackages) luacheck vusted; + in { + packages = { + default = vimUtils.buildVimPlugin { + name = "lspcontainers.nvim"; + src = ./.; + }; + + neovim = neovim.override { + configure = { + customRC = '' + lua << EOF + ${(builtins.readFile ./init.lua)}; + EOF + ''; + packages.main = with vimPlugins; { + start = [ + nvim-lspconfig + config.packages.default + ]; + }; }; }; }; - }); - devShells = forAllSystems (system: { - default = pkgs.${system}.mkShellNoCC { - buildInputs = [ self.packages.${system}.neovim ]; + devShells = { + default = mkShell { + nativeBuildInputs = [just luacheck vusted]; + }; }; - }); - apps = forAllSystems (system: { - neovim = { - program = "${self.packages.${system}.neovim}/bin/nvim"; - type = "app"; + apps = { + neovim = { + program = "${config.packages.neovim}/bin/nvim"; + type = "app"; + }; + + test = { + program = "${config.packages.test}/bin/test"; + type = "app"; + }; }; - }); + }; }; } diff --git a/justfile b/justfile new file mode 100644 index 0000000..9217c6a --- /dev/null +++ b/justfile @@ -0,0 +1,19 @@ +_default: + just --list + +build profile="default": + nix build --json --no-link --print-build-logs ".#{{ profile }}" + +check: + nix flake check + +test: test-lint test-unit + +test-lint: + luacheck ./lua ./test + +test-unit: + #!/usr/bin/env bash + set -euxo pipefail + export VUSTED_NVIM=$(just build 'neovim' | jq -r '.[].outputs.out')/bin/nvim + vusted ./test diff --git a/lua/lspcontainers/init.lua b/lua/lspcontainers/init.lua index 82702ea..8d8965e 100644 --- a/lua/lspcontainers/init.lua +++ b/lua/lspcontainers/init.lua @@ -1,208 +1,209 @@ Config = { - ensure_installed = {} + ensure_installed = {} } local supported_languages = { - bashls = { image = "docker.io/lspcontainers/bash-language-server" }, - clangd = { image = "docker.io/lspcontainers/clangd-language-server" }, - denols = { image = "docker.io/lspcontainers/denols" }, - dockerls = { image = "docker.io/lspcontainers/docker-language-server" }, - gopls = { - cmd_builder = function (runtime, workdir, image, network) - local volume = workdir..":"..workdir..":z" - local env = vim.api.nvim_eval('environ()') - local gopath = env.GOPATH or env.HOME.."/go" - local gopath_volume = gopath..":"..gopath..":z" - - local group_handle = io.popen("id -g") - local user_handle = io.popen("id -u") - - local group_id = string.gsub(group_handle:read("*a"), "%s+", "") - local user_id = string.gsub(user_handle:read("*a"), "%s+", "") - - group_handle:close() - user_handle:close() - - local user = user_id..":"..group_id - - if runtime == "docker" then - network = "bridge" - elseif runtime == "podman" then - network = "slirp4netns" - end - - return { + bashls = { image = "docker.io/lspcontainers/bash-language-server" }, + clangd = { image = "docker.io/lspcontainers/clangd-language-server" }, + denols = { image = "docker.io/lspcontainers/denols" }, + dockerls = { image = "docker.io/lspcontainers/docker-language-server" }, + gopls = { + cmd_builder = function(runtime, workdir, image, network) + local volume = workdir .. ":" .. workdir .. ":z" + local env = vim.api.nvim_eval('environ()') + local gopath = env.GOPATH or env.HOME .. "/go" + local gopath_volume = gopath .. ":" .. gopath .. ":z" + + local group_handle = io.popen("id -g") + local user_handle = io.popen("id -u") + + local group_id = string.gsub(group_handle:read("*a"), "%s+", "") + local user_id = string.gsub(user_handle:read("*a"), "%s+", "") + + group_handle:close() + user_handle:close() + + local user = user_id .. ":" .. group_id + + if runtime == "docker" then + network = "bridge" + elseif runtime == "podman" then + network = "slirp4netns" + end + + return { + runtime, + "container", + "run", + "--env", + "GOPATH=" .. gopath, + "--interactive", + "--network=" .. network, + "--rm", + "--workdir=" .. workdir, + "--volume=" .. volume, + "--volume=" .. gopath_volume, + "--user=" .. user, + image + } + end, + image = "docker.io/lspcontainers/gopls", + }, + graphql = { image = "docker.io/lspcontainers/graphql-language-service-cli" }, + html = { image = "docker.io/lspcontainers/html-language-server" }, + intelephense = { image = "docker.io/lspcontainers/intelephense" }, + jsonls = { image = "docker.io/lspcontainers/json-language-server" }, + omnisharp = { image = "docker.io/lspcontainers/omnisharp" }, + powershell_es = { image = "docker.io/lspcontainers/powershell-language-server" }, + prismals = { image = "docker.io/lspcontainers/prisma-language-server" }, + pylsp = { image = "docker.io/lspcontainers/python-lsp-server" }, + pyright = { image = "docker.io/lspcontainers/pyright-langserver" }, + rust_analyzer = { image = "docker.io/lspcontainers/rust-analyzer" }, + solargraph = { image = "docker.io/lspcontainers/solargraph" }, + lemminx = { image = "docker.io/lspcontainers/lemminx" }, + lua_ls = { image = "docker.io/lspcontainers/lua-language-server" }, + svelte = { image = "docker.io/lspcontainers/svelte-language-server" }, + tailwindcss = { image = "docker.io/lspcontainers/tailwindcss-language-server" }, + terraformls = { image = "docker.io/lspcontainers/terraform-ls" }, + tsserver = { image = "docker.io/lspcontainers/typescript-language-server" }, + vuels = { image = "docker.io/lspcontainers/vue-language-server" }, + yamlls = { image = "docker.io/lspcontainers/yaml-language-server" }, +} + +-- convert dos path to unix path +local function dos2UnixSafePath(workdir) + workdir = string.gsub(workdir, ":", "") + workdir = string.gsub(workdir, "\\", "/") + workdir = "/" .. workdir + return workdir +end + +-- default command to run the lsp container +local default_cmd = function(runtime, workdir, image, network, docker_volume) + if vim.loop.os_uname().sysname == "Windows_NT" then + workdir = dos2UnixSafePath(workdir) + end + + local mnt_volume + if docker_volume ~= nil then + mnt_volume = "--volume=" .. docker_volume .. ":" .. workdir .. ":z" + else + mnt_volume = "--volume=" .. workdir .. ":" .. workdir .. ":z" + end + + return { runtime, "container", "run", - "--env", - "GOPATH="..gopath, "--interactive", - "--network="..network, "--rm", - "--workdir="..workdir, - "--volume="..volume, - "--volume="..gopath_volume, - "--user="..user, + "--network=" .. network, + "--workdir=" .. workdir, + mnt_volume, image - } - end, - image = "docker.io/lspcontainers/gopls", - }, - graphql = { image = "docker.io/lspcontainers/graphql-language-service-cli" }, - html = { image = "docker.io/lspcontainers/html-language-server" }, - intelephense = { image = "docker.io/lspcontainers/intelephense" }, - jsonls = { image = "docker.io/lspcontainers/json-language-server" }, - omnisharp = { image = "docker.io/lspcontainers/omnisharp" }, - powershell_es = { image = "docker.io/lspcontainers/powershell-language-server" }, - prismals = { image = "docker.io/lspcontainers/prisma-language-server" }, - pylsp = { image = "docker.io/lspcontainers/python-lsp-server" }, - pyright = { image = "docker.io/lspcontainers/pyright-langserver" }, - rust_analyzer = { image = "docker.io/lspcontainers/rust-analyzer" }, - solargraph = { image = "docker.io/lspcontainers/solargraph" }, - lemminx = { image = "docker.io/lspcontainers/lemminx" }, - lua_ls = { image = "docker.io/lspcontainers/lua-language-server" }, - svelte = { image = "docker.io/lspcontainers/svelte-language-server" }, - tailwindcss= { image = "docker.io/lspcontainers/tailwindcss-language-server" }, - terraformls = { image = "docker.io/lspcontainers/terraform-ls" }, - tsserver = { image = "docker.io/lspcontainers/typescript-language-server" }, - vuels = { image = "docker.io/lspcontainers/vue-language-server" }, - yamlls = { image = "docker.io/lspcontainers/yaml-language-server" }, -} - --- default command to run the lsp container -local default_cmd = function (runtime, workdir, image, network, docker_volume) - if vim.loop.os_uname().sysname == "Windows_NT" then - workdir = Dos2UnixSafePath(workdir) - end - - local mnt_volume - if docker_volume ~= nil then - mnt_volume ="--volume="..docker_volume..":"..workdir..":z" - else - mnt_volume = "--volume="..workdir..":"..workdir..":z" - end - - return { - runtime, - "container", - "run", - "--interactive", - "--rm", - "--network="..network, - "--workdir="..workdir, - mnt_volume, - image - } + } end local function command(server, user_opts) - -- Start out with the default values: - local opts = { - container_runtime = "docker", - root_dir = vim.fn.getcwd(), - cmd_builder = default_cmd, - network = "none", - docker_volume = nil, - } - - -- If the LSP is known, it override the defaults: - if supported_languages[server] ~= nil then - opts = vim.tbl_extend("force", opts, supported_languages[server]) - end - - -- If any opts were passed, those override the defaults: - if user_opts ~= nil then - opts = vim.tbl_extend("force", opts, user_opts) - end - - if not opts.image then - error(string.format("lspcontainers: no image specified for `%s`", server)) - return 1 - end - - return opts.cmd_builder(opts.container_runtime, opts.root_dir, opts.image, opts.network, opts.docker_volume) -end + -- Start out with the default values: + local opts = { + container_runtime = "docker", + root_dir = vim.fn.getcwd(), + cmd_builder = default_cmd, + network = "none", + docker_volume = nil, + } + + -- If the LSP is known, it override the defaults: + if supported_languages[server] ~= nil then + opts = vim.tbl_extend("force", opts, supported_languages[server]) + end + + -- If any opts were passed, those override the defaults: + if user_opts ~= nil then + opts = vim.tbl_extend("force", opts, user_opts) + end -Dos2UnixSafePath = function(workdir) - workdir = string.gsub(workdir, ":", "") - workdir = string.gsub(workdir, "\\", "/") - workdir = "/" .. workdir - return workdir + if not opts.image then + error(string.format("lspcontainers: no image specified for `%s`", server)) + return 1 + end + + return opts.cmd_builder(opts.container_runtime, opts.root_dir, opts.image, opts.network, opts.docker_volume) end local function on_event(_, data, event) - --if event == "stdout" or event == "stderr" then - if event == "stdout" then - if data then - for _, v in pairs(data) do - print(v) - end + --if event == "stdout" or event == "stderr" then + if event == "stdout" then + if data then + for _, v in pairs(data) do + print(v) + end + end end - end end local function images_pull(runtime) - local jobs = {} - if not (type(runtime) == "string") then runtime = "docker" end - - for idx, server_name in ipairs(Config.ensure_installed) do - local server = supported_languages[server_name] - - local job_id = - vim.fn.jobstart( - runtime.." image pull "..server['image'], - { - on_stderr = on_event, - on_stdout = on_event, - on_exit = on_event, - } - ) - - table.insert(jobs, idx, job_id) - end + local jobs = {} + if type(runtime) ~= "string" then runtime = "docker" end + + for idx, server_name in ipairs(Config.ensure_installed) do + local server = supported_languages[server_name] + + local job_id = + vim.fn.jobstart( + runtime .. " image pull " .. server['image'], + { + on_stderr = on_event, + on_stdout = on_event, + on_exit = on_event, + } + ) + + table.insert(jobs, idx, job_id) + end - local _ = vim.fn.jobwait(jobs) + local _ = vim.fn.jobwait(jobs) - print("lspcontainers: Language servers successfully pulled") + print("lspcontainers: Language servers successfully pulled") end local function images_remove(runtime) - local jobs = {} - if not (type(runtime) == "string") then runtime = "docker" end - - for _, v in pairs(supported_languages) do - local job = - vim.fn.jobstart( - runtime.." image rm --force "..v['image']..":latest", - { - on_stderr = on_event, - on_stdout = on_event, - on_exit = on_event, - } - ) - - table.insert(jobs, job) - end - - local _ = vim.fn.jobwait(jobs) - - print("lspcontainers: All language servers removed") + local jobs = {} + if type(runtime) ~= "string" then runtime = "docker" end + + for _, v in pairs(supported_languages) do + local job = + vim.fn.jobstart( + runtime .. " image rm --force " .. v['image'] .. ":latest", + { + on_stderr = on_event, + on_stdout = on_event, + on_exit = on_event, + } + ) + + table.insert(jobs, job) + end + + local _ = vim.fn.jobwait(jobs) + + print("lspcontainers: All language servers removed") end vim.api.nvim_create_user_command("LspImagesPull", images_pull, {}) vim.api.nvim_create_user_command("LspImagesRemove", images_remove, {}) local function setup(options) - if options['ensure_installed'] then - Config.ensure_installed = options['ensure_installed'] - end + if options['ensure_installed'] then + Config.ensure_installed = options['ensure_installed'] + end end return { - command = command, - images_pull = images_pull, - images_remove = images_remove, - setup = setup, - supported_languages = supported_languages + command = command, + images_pull = images_pull, + images_remove = images_remove, + setup = setup, + supported_languages = supported_languages } diff --git a/scripts/test.lua b/scripts/test.lua deleted file mode 100644 index ecb8b3c..0000000 --- a/scripts/test.lua +++ /dev/null @@ -1,30 +0,0 @@ -local configs = require 'lspconfig.configs' -local lspcontainers = require 'lspcontainers' - --- Copied from https://github.com/neovim/nvim-lspconfig/blob/master/scripts/docgen.lua -local function require_all_configs() - -- Configs are lazy-loaded, tickle them to populate the `configs` singleton. - for _, v in ipairs(vim.fn.glob('nvim-lspconfig/lua/lspconfig/server_configurations/*.lua', 1, 1)) do - local module_name = v:gsub('.*/', ''):gsub('%.lua$', '') - configs[module_name] = require('lspconfig.server_configurations.' .. module_name) - end -end - -require_all_configs() -local known_lsp_configs = vim.tbl_keys(configs) - -local test_succeed = true -local emoji_ok = '✅' -local emoji_bad = '❌' - -for language_server, _ in pairs(lspcontainers.supported_languages) do - if vim.tbl_contains(known_lsp_configs, language_server) then - print(language_server, emoji_ok) - else - print(language_server, emoji_bad) - test_succeed = false - end -end - -print("\n") -os.exit(test_succeed) diff --git a/scripts/test.sh b/scripts/test.sh deleted file mode 100755 index dada62f..0000000 --- a/scripts/test.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -euo 'pipefail' - -parent_git_dir=$(git rev-parse --show-toplevel) -cd "${parent_git_dir}" - -if [ ! -d nvim-lspconfig ]; then - git clone --depth=1 https://github.com/neovim/nvim-lspconfig/ -fi - -exec nvim -u NONE -E -R --headless +"set rtp+=$PWD" +"set rtp+=nvim-lspconfig" +'luafile scripts/test.lua' +q -# nvim -u scripts/test_keys.lua diff --git a/test/language_servers_spec.lua b/test/language_servers_spec.lua new file mode 100644 index 0000000..99287b7 --- /dev/null +++ b/test/language_servers_spec.lua @@ -0,0 +1,33 @@ +local configs = require 'lspconfig.configs' +local lspcontainers = require 'lspcontainers' + +-- Inspired from https://github.com/neovim/nvim-lspconfig/blob/master/scripts/docgen.lua +local function require_supported_configs() + -- Configs are lazy-loaded, tickle them to populate the `configs` singleton. + for language_server, _ in pairs(lspcontainers.supported_languages) do + configs[language_server] = require('lspconfig.server_configurations.' .. language_server) + end +end + +require_supported_configs() + +describe('lspcontainers', function() + it('should have lspconfig support for each', function() + local emoji_bad = '❌' + local emoji_ok = '✅' + local not_found = 0 + + for lsp_name, _ in pairs(lspcontainers.supported_languages) do + if vim.tbl_contains(vim.tbl_keys(configs), lsp_name) then + print(lsp_name, emoji_ok) + else + print(lsp_name, emoji_bad) + not_found = not_found + 1 + end + end + + print("\n") + + assert.are.same(0, not_found) + end) +end)