Giter Club home page Giter Club logo

lazy-lsp.nvim's Introduction

lazy-lsp.nvim

Neovim plugin to automatically install nvim-lspconfig language servers. It is an alternative to mason.nvim.

To ease the setup even further it can be complemented with lsp-zero.nvim.

Language servers are loaded in the background without a need of a manual user intervention. They are not installed upfront, but only on-demand after a source file for a given language is opened. The plugin works by relying on Nix package manager which works on Linux, macOS and Windows WSL.

Currently available 116 out of 301 servers in lspconfig, see the full list of supported servers.

Install

Requires Neovim v0.7.0+

  1. Install Nix package manager: curl -L https://nixos.org/nix/install | sh
  2. Install the plugin using lazy.nvim:
{
  "dundalek/lazy-lsp.nvim",
  dependencies = { "neovim/nvim-lspconfig" },
  config = function()
    require("lazy-lsp").setup {}
  end
},

Alternatively, you can install the plugin via Nix/Home Manager.

  1. That's it, nothing else to install!

Setup

Quickest way to configure is to use lsp-zero.nvim which sets up key bindings and autocompletion.

{
  "dundalek/lazy-lsp.nvim",
  dependencies = {
    "neovim/nvim-lspconfig",
    {"VonHeikemen/lsp-zero.nvim", branch = "v3.x"},
    "hrsh7th/cmp-nvim-lsp",
    "hrsh7th/nvim-cmp",
  },
  config = function()
    local lsp_zero = require("lsp-zero")

    lsp_zero.on_attach(function(client, bufnr)
      -- see :help lsp-zero-keybindings to learn the available actions
      lsp_zero.default_keymaps({
        buffer = bufnr,
        preserve_mappings = false
      })
    end)

    require("lazy-lsp").setup {}
  end,
},

Another approach is to configure manually by passing on_attach handler which sets up keybindings and capabilities for autocompletion in the default_config section.

Available configuration options:

require("lazy-lsp").setup {
  -- By default all available servers are set up. Exclude unwanted or misbehaving servers.
  excluded_servers = {
    "ccls", "zk",
  },
  -- Alternatively specify preferred servers for a filetype (others will be ignored).
  preferred_servers = {
    markdown = {},
    python = { "pyright", "ruff_lsp" },
  },
  prefer_local = true, -- Prefer locally installed servers over nix-shell
  -- Default config passed to all servers to specify on_attach callback and other options.
  default_config = {
    flags = {
      debounce_text_changes = 150,
    },
    -- on_attach = on_attach,
    -- capabilities = capabilities,
  },
  -- Override config for specific servers that will passed down to lspconfig setup.
  -- Note that the default_config will be merged with this specific configuration so you don't need to specify everything twice.
  configs = {
    lua_ls = {
      settings = {
        Lua = {
          diagnostics = {
            -- Get the language server to recognize the `vim` global
            globals = { "vim" },
          },
        },
      },
    },
  },
}

Curated servers

The philosophy of this plugin is to enable all possible plugins by default to get the highest chance of LSP functionality being available, even at a cost of starting multiple servers for a single language. Any misbehaving or unwanted servers can be excluded one by one.

If this is not what you want, you can give a try to the curated configuration that enables smaller selection of recommended servers.

How it works

lazy-lsp registers all available configurations from lspconfig to start LSP servers by wrapping the commands in a nix-shell environment. The nix-shell prepares the environment by pulling all specified dependencies regardless of what is installed on the host system and avoids packages clashes. The first time a server is run there is a delay until dependencies are downloaded, but on subsequent runs the time to prepare the shell environment is negligible.

Versions

Nix uses channels to determine which packages and versions are available. I recommend using the unstable channel to get the latest versions.

If you encounter an older version of a language server try to run nix-channel --update to update channels. See docs about channels for more details how to work with channels.

Comparison with alternatives

  • Installing manually, or via language specific package managers like npm, pip, etc. is a hassle.
  • nvim-lsp-installer / mason.nvim
    • Pro: Supports Windows natively
    • Pro: Supports more servers at the moment
    • Con: :LspInstall command needs to be run manually for each server
      (vs. automatic installation in the background)
    • Con: Additional dependencies like npm, pip, etc. need to be installed separately
      (vs. a single Nix dependency)
    • Con: Maintaining cross-platform installation scripts is a large maintanance burden
      (vs. leveraging work by a large Nix community specializing in software packaging)
  • lspcontainers.nvim - uses docker containers to run servers in a portable way which comes with an extra overhead

Development

The available lsp servers and stats are generated from nvim-lspconfig source.

Make a local copy:

mkdir tmp && cd tmp
git clone https://github.com/neovim/nvim-lspconfig.git

Run nix-shell to load dependencies needed for running the dev scripts.

Update servers.lua with new entries:

cd tmp/nvim-lspconfig
git pull
cd ../..
scripts/update.lua

Once done specifying servers, generate the stats in servers.md:

scripts/genstats.lua

Testing

Using Plenary for testing, see the Testing Guide for details.

Run tests:

scripts/test

Run tests in watch mode:

scripts/test-watch

lazy-lsp.nvim's People

Contributors

dundalek avatar fdietze avatar jammus avatar kamalmarhubi avatar marcusramberg avatar metiulekm avatar rickypowell avatar zoriya avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

lazy-lsp.nvim's Issues

Preferred LSP for languages

Hi, I love the idea behind this plugin, but I find myself excluding lots of servers that I don't care about/that are buggy.
For reference, my exclude_servers looks like this:

excluded_servers = {
	-- Disable generic purpose LSP that I don't care about.
	"efm",
	"diagnosticls",
	-- Prefer tsserver
	"denols",
	"eslint",
	-- Prefer nix_ls (more mature)
	"rnix",
	-- Prefer pyright
	"pylsp",
	"jedi_language_server",
	-- rls is deprecated, rust_analyzer should be used instead.
	"rls",
	-- Prefer clangd
	"ccls",
}

This is not optimal since I can't be sure a new conflicting LSP has been added every time I update my nvim configs. Furthermore, every time I open a file in a new programming language, there are 50% chances that a broken LSP starts and throw an error.

I propose two things:

  1. Add a preferred_server category to associate a file type to an LSP by default (for example, we could write python = "pyright" instead of excluding "pylsp" and "jedi_language_server" as in my example config).
  2. Add default preferred_servers that marks the most used server of each language, so we don't have to mark a custom preferred for each language.

Support for Linters and Formatters

Hi,

I've been using this plugin for months and I'm enjoying it. It has changed how I use LSPs in Neovim, but I think that this plugin would be more useful if linters and formatters were included (since nixpkgs seems to have packages for them.) Thank you for your time making this plugin!

Rust LSP invalidates cargo cache, or fails

Firstly, thank you so much for creating this! It's such an elegant solution to LSP installs on NixOS. I'm stuck between a rock and a hard place, and I'm hoping you can point me in the right direction.

Without having cargo installed if I launch nvim on main.rs it loads rust-analyser as I would expect, but shows LSP underlines on things like println! and is unable to provide symbol info with Shift+K. I don't see anything in :LspLog though. Same thing happens if I install cargo either in a flake or global with the standard nix packages. If I instead add https://github.com/oxalica/rust-overlay the LSP works as expected, happily returning Shift+K symbol info. But the LSP seems to invalidate the entire cargo build, so ever time I run cargo build it starts from scratch which takes a long time on big projects.

I'm stuck between having a functional LSP, and having super long builds for every single tiny change. I could never get Rust to work right with Mason, and think this is a better solution anyway.

Any ideas on where to look? I've stripped my nvim config down to just the recommend on your README.md, so I don't think it's another plugin causing issues. I'm on nvim 0.9 if that seems relevant.

Interaction between `preferred_servers` and `excluded_servers` has changed a bit recently

So I had something like this in my config:

-- rust_analyzer setup via nvim-lspconfig

require("lazy-lsp").setup {
    excluded_servers = {
		"rust_analyzer",
	}
    preferred_servers = {
        rust = { "rust_analyzer" },
	}
}

The intention behind this weird piece of config was that I wanted to setup rust_analyzer manually, essentially due to #22. But back when I was writing this config, there was also the legacy Rust language server, RLS, which I wanted to exclude. And at the time, I wanted to use preferred_servers to set my preferred servers instead of excluding those that I did not want (not unlike #23 (comment)).

In the past, this worked exactly as I wanted: lazy-lsp would not setup rust_analyzer because I have excluded it, and would also setup nothing else for this filetype because of the preferred_servers value. So in the end, lazy-lsp would not do anything for the rust filetype.

However, some time ago, lazy-lsp started instead setting up a second copy of rust-analyzer. I did not investigate this very deeply, but I suspect this is due to 26c681f. If I understand correctly, on the left side we iterate on included_servers, which takes excluded_servers into account. However, on the right side, we iterate on server_to_filetypes, which also takes preferred_servers into account.

In the end, I just switched to using prefer_local (thanks a lot! โค๏ธ). Also I changed my mind on excluded_servers/preferred_servers, and you have removed RLS configuration anyway. And even then I will be first to admit that the snippet I pasted looks kind of absurd, so I won't be surprised if you just close the issue immediately ๐Ÿ˜› But I do feel the previous behavior is slightly more correct, and I just wanted to create this issue to not throw away information.

getting status of daemon socket access denied

when i open any file that lazy lsp should work on, i get Client 2 quit with exit code 1 and signal 0.

when I run :LspLog I get:

error getting status of /nix/var/nix/daemon-socket/socket: Permission denied.

Im on Arch linux and installed nix from the Arch repository

Question: How to debug and/or customize version?

I noticed that the clangd version that is provided is 11. When i was using clangd with coc.nvim it was auto-installing clangd 14. I would like to know what would be involved to customize/update something like this in this framework. I do not know anything about nix so the lack of documentation is a little challenging.

Love it

This really makes LSP experience on vim better. Wish someone did similar for syntax highlighting.

[lspconfig] Cannot access configuration for `server`

I don't know why, but every time I open Neovim after installing this plugin I get the following error (doesn't matter what type of file I'm opening):

[lspconfig] Cannot access configuration for docker_compose_language_service. Ensure this server is listed in `server_configurations.md` or added as a custom server.
[lspconfig] Cannot access configuration for docker_compose_language_service. Ensure this server is listed in `server_configurations.md` or added as a custom server.
Error in packer_compiled: ...pack/packer/start/lazy-lsp.nvim/lua/lazy-lsp/helpers.lua:16: attempt to index field 'document_config' (a nil value)
Please check your config for correctness

And it always shows a different server name that it cannot access the configuration.

I'm running Neovim v0.8.1 on NixOS 22.11 with the 2023.02.27 version of the plugin, I've already tested it with Neovim Nightly or using the master branch, but I've got the same errors.

Here's my configuration:

require("lazy-lsp").setup {
	excluded_servers = {
		-- prefer ccls
		"clangd",
	},

	-- Default options
	default_config = {
		on_attach = on_attach,
	},

	-- Custom options
	configs = {
		sumneko_lua = {
			cmd = { "lua-language-server" },
			settings = {
				Lua = {
					runtime = {
						version = "LuaJIT",
						path = sumneko_runtime_path
					},
					diagnostics = {
						disable = { "codestyle-check", "spell-check" },
						globals = { "vim", "use", "require" }
					},
					completion = {
						enable = true,
						callSnippet = "Disable",
						keywordSnippet = "Disable"
					},
					format = { enable = false },
					hint = { enable = false },
					hover = { enable = false },
					workspace = {
						checkThirdParty = true,
						library = vim.api.nvim_get_runtime_file("", true)
					},
					telemetry = { enable = false }
				}
			}
		},
		pyright = {
			python = {
				analysis = {
					autoSearchPaths = true,
					diagnosticMode = "workspace",
					useLibraryCodeForTypes = true
				}
			}
		}
	}
}

Add an option to override used nix package

Came up in #6 . The issue got fixed by making updates to the plugin and I argued that not having the option will incentivize people to report issues which will make the plugin better for everyone.

But in #13 (comment) there came up another need due to mismatch when using different nix channels. This is out of the plugin control (besides maintaining different set of configs for different channels which I wouldn't want getting into), so providing an option to override for the user would make sense.

One remaining decision remains what the option should be. The issue is that currently we just pass the options through to the lspcofing. By adding an option we could end up mixing options, which could be confusing or leading to potential conflicts. I can think of two options:

  1. Add a key under configs for a given server like nix_pkg. We then remove the key from table before passing it down to lspconfig. There is a risk options could conflict.
    1b. Variant of 1. to increase the chance the option stays unique, we could use even more unique name like lazy_lsp_nix_pkg.
  2. Add separate option like packages which would make lsp configuration and package configuration independent, there would not be any potential for conflict. It is less convenient since the server name will end up duplicated, but it should be in rare situations (e.g. when being on a different nix channel).
packages = {
  lua_ls = "sumneko-lua-language-server"
}

In the trade-off between confusion vs less conveniece (duplicate server name), I am slightly leaning towards 2.

Load error: attempt to index field 'document_config' (a nil value)

Hi! I can't seem to make this work, with the default config (below) on either NeoVim 0.9 or 0.10. I haven't finished ruling out something weird about my NixOS setup that's causing it yet.

Full init.lua

-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", -- latest stable release
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
  {
    "dundalek/lazy-lsp.nvim",
    dependencies = { "neovim/nvim-lspconfig" },
    config = function()
      require("lazy-lsp").setup {}
    end
  },
})

Output of :messages

ruby_ls is deprecated, use ruby_lsp instead.
This feature will be removed in lspconfig version 0.2.0
Failed to run `config` for lazy-lsp.nvim
...l/share/nvim/lazy/lazy-lsp.nvim/lua/lazy-lsp/helpers.lua:73: attempt to index field 'document_config' (a nil value)
# stacktrace:
  - /lazy-lsp.nvim/lua/lazy-lsp/helpers.lua:73 _in_ **build_filetype_to_servers_index**
  - /lazy-lsp.nvim/lua/lazy-lsp/helpers.lua:109 _in_ **enabled_filetypes_to_servers**
  - /lazy-lsp.nvim/lua/lazy-lsp/helpers.lua:125 _in_ **server_configs**
  - /lazy-lsp.nvim/lua/lazy-lsp/init.lua:7 _in_ **setup**
  - init.lua:21 _in_ **config**
  - init.lua:16

The part about ruby_ls leads me to believe this might be something weird with my own system? Hoping someone has seen this before and can point me in the right direction. I've gone full NixOS, and Mason really doesn't work with it.

sqls -> sqlls

sqls has been archived on GitHub https://github.com/lighttiger2505/sqls

So lsp-config has marked it deprecated neovim/nvim-lspconfig#2544 which splashes a warning message each time you start nvim.

They recommend using "sqlls" https://github.com/joe-re/sql-language-server instead, but I don't see a nix package for it yet unfortunately.

I tried adding it to excluded_servers but it still pops up the warning. It'd be nice if there was a way to just ignore it, but I'm not sure what to try. Do you have any ideas?

Load time when active (on WSL Windows)

There's any reason that would explain the increase launch time of neovim when using this plugin?

My usual start time is around 30ms (with mason+lsp+cmp). If I use the default configuration in this project's README.md, two things happen.

  1. The start time of neovim goes to around 2~3 seconds
  2. Always get the following error
[lsp-zero] Some language servers have been configured before
lsp-zero could finish its initial setup. Some features may fail.

Details on how to solve this problem are in the help page.
Execute the following command

:help lsp-zero-guide:fix-extend-lspconfig
Press ENTER or type command to continue

The config I'm using is this test installation:

return {
  {
    "dundalek/lazy-lsp.nvim",
    dependencies = {
      "neovim/nvim-lspconfig",
      { "VonHeikemen/lsp-zero.nvim", branch = "v3.x" },
      "hrsh7th/cmp-nvim-lsp",
      "hrsh7th/nvim-cmp",
    },
    config = function()
      local lsp_zero = require("lsp-zero")

      lsp_zero.on_attach(function(client, bufnr)
        -- see :help lsp-zero-keybindings to learn the available actions
        lsp_zero.default_keymaps({
          buffer = bufnr,
          preserve_mappings = false,
        })
      end)

      require("lazy-lsp").setup({})
    end,
  },
}

My entire config can be found here: https://github.com/renantmagalhaes/workstation/tree/master/desktop/source/any/config/nvim

Current lsp.lua is under: https://github.com/renantmagalhaes/workstation/blob/master/desktop/source/any/config/nvim/lua/plugins/lsp.lua

Flakes support

This is an issue to collect feedback and notes about Flakes. We should consider using them in the future, but there might be a few things that need to be figured out.

Motivation:

  • Possible to more easily include lsps not packaged in nixpkgs yet
  • Ability to lock specific versions
    • This is especially important, if a lsp is tight to a specific compiler version used in the project, like ghc for example.

  • Potentially faster start due to flake input caching

Obstacles:

  • Flakes are still marked experimental
  • Not enabled by default, is there a way to make sure they are enabled without user intervention? Any gotchas with --experimental-features 'nix-command flakes'?

Questions:

  • Do we want to use flakes just internally without any visible impact on the user?
  • If we enable for a user to customize flake-related behavior, what kind of options should we expose?

I've seen a proof-of-concept in a fork. Is this a good solution we could build upon? kamalmarhubi@ebb1543

Add an option to disable notification warnings

I know registering LSPs into nix-shell environment is a great idea but it still has latency (maybe 4 or 5 seconds). In those seconds the lspconfig will show out this warning
image
(Even I tried to exit neovim and open it again several times but this warning still poped up)
That's feel annoying to me or anyone will use this plugin in the future, so I think you should add an option to disable these warnings, or manage to download the LSP via nix then call lspconfig after, thanks.

Add unit tests

The logic of picking merging configurations starts to get complicated, will need to figure out how to unit test neovim plugins.

Consider taking `PATH` into account

I think that launching the client directly if it can be found in PATH might be a good idea. I can think of a couple of usecases:

  1. This way, people who need to install some servers manually for some reason can still configure them using your plugin. Right now, they need to configure them using nvim-lspconfig and then tell your plugin to not configure them, which adds some duplication (or at least LoC).
  2. Some people might use a different copy of the language server for a project. AFAIK this is quite common in Haskell for example, since HLS is tightly bound to the compiler. Therefore sometimes Haskell projects which provide a Nix shell also add the correct build of HLS to the development shell (example: https://github.com/fjvallarino/monomer/blob/05c5d6c9b5d0649ad5da59b67cb0328fe82e7ed6/flake.nix#L36) (though this might still work as is, because HLS uses a wrapper to launch the correct version depending on the compiler version, but I haven't tested this; still, in the best case we would download a whole new version of HLS and never use it).

Community curated list of preferred servers?

The philosophy of this plugin is to have as much as possible to work automatically without configuration and only correct when few misbehaving instances. Based on discussion in #17 people might prefer to sacrifice some lsps if it means being exposed to less potential issues.

Should we curate a list of recommended servers, e.g. pick one server when there are multiple alternatives or exclude known misbehaving ones to reduce number of issues people can run into?

For example I recall nvim-treesitter having ensure_installed that could take value "all" or "maintained" to restrict available grammars. Would it make sense to have something similar? We could have by default a "recommended" selection while providing the option to opt-in to all servers (which is the current default).

rust: add `cargo` and `rustfmt`?

Having a clean nixos system (no cargo etc installed) and opening a rust file in a rust project, rust-analyzer complains that it doesn't find cargo. Does it make sense to add it to the nix-shell? Same for rustfmt, which allows the lsp to format the buffer.

rust_analyzer = "rust-analyzer",

I wanted to test this locally, but didn't get it working. I'm using lazy.nvim and edited the servers.lua file in the installed plugin, but didn't see any effect. Any hints?

sumneko_lua has been renamed

I've recently getting this error after updating all my plugins, and I think it's because of neovim/nvim-lspconfig#2439

sumneko_lua is deprecated, use lua_ls instead. See :h deprecated
This function will be removed in lspconfig version 0.2.0
stack traceback:
        .../.local/share/nvim/lazy/nvim-lspconfig/lua/lspconfig.lua:36: in function '__index'
        ...ll/.local/share/nvim/lazy/lazy-lsp.nvim/lua/lazy-lsp.lua:23: in function 'setup'
        /Users/will/.config/nvim/lua/user/lsp.lua:127: in main chunk
        [C]: in function 'require'
        /Users/will/code/dotfiles/config/nvim/init.lua:5: in main chunk
Error detected while processing /Users/will/code/dotfiles/config/nvim/init.lua:
E5113: Error while calling lua chunk: ...ll/.local/share/nvim/lazy/lazy-lsp.nvim/lua/lazy-lsp.lua:26: attempt to index field 'document_config' (a nil value)
stack traceback:
        ...ll/.local/share/nvim/lazy/lazy-lsp.nvim/lua/lazy-lsp.lua:26: in function 'setup'
        /Users/will/.config/nvim/lua/user/lsp.lua:127: in main chunk
        [C]: in function 'require'
        /Users/will/code/dotfiles/config/nvim/init.lua:5: in main chunk

I poked a bit at just manually renaming it a PR but then saw all the update scripts and whatnot, and I wasn't sure if that would be better

Startup time optimization

Loading the plugin adds roughly following time penalty (measured on Linux, likely a bit slower on other OSes):

  1. ~30ms loading individual lsp configs (~120ms on WSL Windows)
  2. 20ms spent in lspconfig.manager afterwards

First optimization would be to reduce 2) by setting up the servers only when a file of a given filetype is opened. This would need figuring out what lspconfig does internally, which events it hooks. done in #31

Second optimization would be to reduce 1). We could build up a mapping from filetype to servers so we don't need to load individual configs upfront. The downside is that we would need to update the plugin more often to make sure it tracks the upstream to make sure we don't miss filetypes.

Help to add robotframework_ls

Hello,

I recently started using nix and love your plugin, I would like to add robotframework_ls to the list of available LSPs, but it is not packaged by nix yet. Robotframework_ls is available as a pip package named robotframework-lsp. Do you know what would be the procedure for that?
Surely there is something simpler and more elegant than forking nix/nixpkgs and creating a .nix file that download the bin from pip but I don't really know where to look at to find more information.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.