Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Lean LSP/IDE setup#36962

shushtain started this conversation inShow and tell
Dec 14, 2025· 2 comments· 8 replies
Discussion options

Lean LSP/IDE setup

Neovim 0.11.5

I still remember my initial confusion and hopelessness with setting up IDE features when I switched to Neovim (back at v0.10). And things are better now. There are good guides on YouTube for the current state of things:v0.11+ (uses Lazy plugin manager) andv0.12 (requiresvim.pack.add()).

However, if things were that easy, you could just copy-paste some config without even watching a video. Here I hope to provide more context for each aspect of LSP/IDE setup, focusing on doing more with less dependencies. Please consider it an attempt to help, not a definitive guide on how one should set up their work environment.

Installing a server

For most servers, there are several options:

  • installing a binary from your package manager/website
  • installing a package fromnpm,cargo, etc
  • usingmason.nvim

I strongly recommend using the first two options, unless:

  • your system doesn't have a package manager
  • you don't have any setup for reproducing your OS configuration (GNU Stow, etc)
  • your config is used on many machines that need to "auto-setup"

There is nothing wrong with Mason, it's a great tool. But I haven't seen a single config that would not become a maze. There always comes the need formason-tool-installer,mason-lspconfig, etc. And then something breaks, and suddenly we have to study three plugins to debug the setup.

Just keep in mind that all you actually need is a binary for your server, either on$PATH, or a full link to the executable. That's all.

Configuring LSP

First of all, there is the magnificentnvim-lspconfig which provides sensible defaults. You may have seen older configs that have elaborate setups around this plugin. As of 2025, you just install the plugin, no setup needed, and all it does is provide configs from itslsp/ folder.

You could also copy configs from those files right into your own.config/nvim/lsp/, or intoafter/lsp/, or intovim.lsp.config("", {}). Here, they are written from low to high priority, so if you do installnvim-lspconfig, considerlsp/ folder to be "taken" by the plugin, and use the last two options, so your overrides "win".

For example, if I choose to install the plugin after all, it will provide this from its ownlsp/typos_lsp.lua:

---@typevim.lsp.Configreturn {cmd= {"typos-lsp"},root_markers= {"typos.toml","_typos.toml",".typos.toml","pyproject.toml","Cargo.toml",  }}

Then I can add some options into myafter/lsp/typos_lsp.lua:

---@typevim.lsp.Configreturn {init_options= {diagnosticSeverity="Hint",  }}

And add even more settings inline:

---"*" stands for "all LSPs"vim.lsp.config("*", {capabilities=require("blink.cmp").get_lsp_capabilities({},true),})

Enabling LSP

Don't forget to enable LSP(s).

---after vim.lsp.config()vim.lsp.enable("typos_lsp")vim.lsp.enable({"ts_ls","rust_analyzer"})

Using LSP

Many things are already enabled by default. See:h lsp-defaults.

These GLOBAL keymaps are created unconditionally when Nvim starts:- "grn" is mapped in Normal mode to|vim.lsp.buf.rename()|- "gra" is mapped in Normal and Visual mode to|vim.lsp.buf.code_action()|- "grr" is mapped in Normal mode to|vim.lsp.buf.references()|- "gri" is mapped in Normal mode to|vim.lsp.buf.implementation()|- "grt" is mapped in Normal mode to|vim.lsp.buf.type_definition()|- "gO" is mapped in Normal mode to|vim.lsp.buf.document_symbol()|-CTRL-S is mapped in Insert mode to|vim.lsp.buf.signature_help()|

If you plan on using native completion, you may want to map it to something more convenient:

vim.keymap.set("i","<C-Space>","<C-x><C-o>")

There are other useful things likevim.lsp.buf.format(),vim.lsp.inlay_hint.enable(),vim.diagnostic.config(),vim.diagnostic.open_float(), etc. I'll leave that for you to discover on your own.

Fixing LSP

:checkhealth vim.lsp is the best way to see what's enabled, what settings are used, and what errors are logged (the path to logs is at the top of buffer). When that doesn't help, temporarily addvim.lsp.set_log_level("debug") to your config, reload Neovim, and check the logs again.

Before debugging further and asking for help, at the very least you need to know the answer to:

  • Is the server binary seen/accessible?
  • Does the LSP attach to the buffer you want?
  • Does the LSP recognize the right project root?
  • Are there logged errors related to the problem?

Additional IDE features

Completion and snippets

Native completion becomes more and more powerful. I believe, one day it will have everything you need and more. Until then,blink.cmp is probably the most sensible option. It covers some edge cases withsnippet expansion anddynamic completions; and delivers many quality-of-life features on top of that. By default, it provides snippet support through nativevim.snippet, but can also work with external engines:mini.snippets andLuaSnip.

Formatting

Many LSPs come with built-in formatters. For those that don't, you could useconform.nvim or DIY it.

DIY example

If the formatter has nostdin support (works only by rewriting files), you will have to makeBufWritePost autocmd, and then re-read the file changed outside Neovim. Letting something rewrite the file completely is not ideal, however. Fortunately, most formatters supportstdin, which we should prefer using:

vim.api.nvim_create_autocmd("BufWritePre", {callback=function(args)---It's a good idea to have a manual toggleifvim.g.u_manual_formattingthenreturnendlocalfiletype=vim.bo[args.buf].filetypelocalfilepath=vim.api.nvim_buf_get_name(args.buf)localcmd=nil---Don't be confused by passing the path to the file.---It's just a convenient way for prettier to know which---parser to use. We will pass actual lines later.iffiletype=="html"thencmd= {"prettier","--stdin-filepath",filepath }elseiffiletype=="json"thencmd= {"prettier","--stdin-filepath",filepath,"--trailing-comma","none"}elsereturnend---Gather lines and call formatter synchronously (max 500 ms delay).locallines=vim.api.nvim_buf_get_lines(args.buf,0,-1,false)localresult=vim.system(cmd, {text=true,stdin=lines }):wait(500)---Typically, as other tools, formatters return code 0 for "success"ifresult.code~=0thenvim.notify(result.code.."/"..result.signal..":"..result.stderr,vim.log.levels.WARN      )returnend---Split output back into lineslines=vim.split(result.stdout,"\n")---Optionally trim leading empty lineswhile#lines>0andlines[1]==""dotable.remove(lines,1)end---Optionally trim trailing empty lineswhile#lines>0andlines[#lines]==""dotable.remove(lines)end---Replace buffer content with formatted linesvim.api.nvim_buf_set_lines(args.buf,0,-1,false,lines)end,})

Linting

Many LSPs come with built-in linters. For those that don't, you could usenvim-lint. I won't encourage you to DIY it because many linters have uniquely structured outputs, and you will have to parse that output into Neovim diagnostics format, which is tedious.

Debugging

Although I don't use any interactive debugger myself, I just know that the answer isnvim-dap.

You must be logged in to vote

Replies: 2 comments 8 replies

Comment options

Great guide, but wanted to point out some important detail about Mason:

There is nothing wrong with Mason, it's a great tool. But I haven't seen a single config that would not become a maze. There always comes the need for mason-tool-installer, mason-lspconfig, etc. And then something breaks, and suddenly we have to study three plugins to debug the setup.

True, usually configs end up being a mess. But that's just because people don't know what they are doing (even the people "teaching" how to setup an ls). Mason just installs the servers and no other plugin is needed. You do need to add mason's directory to the path with one line:

vim.env.PATH=vim.fn.stdpath('data')..'/mason/bin'.. (vim.fn.has('win32')~=0and';'or':')..vim.env.PATH

and then you can use the servers. All other plugins just make "easier" to enable ls, as they do it automatically. But I agree with you that with the new interface you should just dovim.lsp.enable("server") and be done with 2 plugins.

As for using Mason or not, there are other important points when Mason is preferable:

  • Not all language servers are available in all package managers. You can of course clone the repos and build yourself, but it's more tedious to keep them up to date.
  • You use multiple OS (specially if using unix and windows). Maintaining systems in different OSs is difficult. Stow is now in windows, and other tools (like ansible) may need different configurations for different OSs. And it's even more difficult to find language servers that are in all the OSs you manage.

Finally, builtin completion works with snippets, neovim has a builtin snippet engine::h vim.snippet. Agree plugins offer more capabilities, but you can just use the builtin if you like what it offers.

You must be logged in to vote
7 replies
@shushtain
Comment options

If I setautotrigger = true, it will expand snippets on confirming a completion with Emmet (fixingolrtg/emmet-language-server#55), but it will also introduce a bug withrust_analyzer (#36823) that sends completeopts more dynamically. Triggering it manually withautotrigger = true fixes both issues, but triggering it manually withautotrigger = false doesn't solve the first one, and basically all other snippets also stop working for me, just inserting a label (abbr), not the actual contents.

If you have ways to solve those issues, I will accept that I was wrong about native completions. Otherwise, its state doesn't seem mature enough to recommend it. Although the following didn't form my opinion on the topic, you can also watch a person struggle with completions in a more entertaining form:https://youtu.be/tnlgQFQi2s4?si=UcrO1nCLwWrMUryh.

Sure we can create a wrapper for each edge case withCompleteDonePre or by changing the core completefunc powering the logic. Sure we can add wrappers to source custom LSP-like snippets from a JSON file by parsing the latter. But that's why I didn't recommend creating your own linter, and that's why I don't recommend touching native completions for now. Althoughblink.cmp, and a fairly newnvim-snippets plugin, just build uponvim.snippet, so I do understand the backend is there.

@SebasF1349
Comment options

I haven't write rust in a while, so I just open an old project and tested what's mentioned in#36823. As you can see, I don't have the same issue:

wezterm-gui_CKjwr6YXWJ

Afaik I don't have anything weird in my setup, so maybe it's something that has been fixed in nightly recently but not in the latest stable. I have never experienced something like that with the ls I use. In any case, if there is any bug it's a good idea to post as an issue so that the team can fix it.

As for the video: these are the issues he had:

  • Autoselection of the first option:completeopt has been a thing for years in vim. Setting an option shouldn't be an obstacle and no custom code is needed.
  • Documentation: yes, you can't see the documentation by default, you need to write some custom code to see it. There is pr right now to add it with a simple opt (feat(lsp): vim.lsp.completion completionItem/resolve when completeopt=popup #32820 - it is stale at the moment because I think it needs another pr to be merged first, hopefully will be merged before the 0.12 release). I don't consider this an issue, many don't use docs (I personally don't, as they are distracting). I do consider this a missing feature, which can be important or not.
  • Can't use luaSnip: Yes, you can't, you can only use lsp snippets. Many ls provide snippets, those can be used. Another option is to provide snippets as an lsp, there are some plugins to do that, for example (nvim-snippets)[https://github.com/garymjr/nvim-snippets]

Sure we can add wrappers to source custom LSP-like snippets from a JSON file by parsing the latter.

I mean, it's a wrapper of just 10 lines of code, not that you need to maintain a 1k monster. But it's just a simple solution if you want to use custom snippets. As I said, there are plugins for that too.

Taking all into account, my whole native completion setup is shorter than your (blink config)[https://github.com/shushtain/dot/blob/main/nvim/.config/nvim/lua/plugins/blink.lua] (and would be the same amount of lines if I added docs), so I don't think it's too complicated to use native.

Yes, as I said before, it has some limitations, and if you are not ok with those limitations there are plugins like blink that offer a lot more. My only comment was that "builtin completion works with snippets", as the original post (before editing) seemed to mention that it wasn't the case ("it adds missing snippet engine").

To be clear, I have never said "builtin is better than a plugin" or something like that. I just said that "Agree plugins offer more capabilities, but you can just use the builtin if you like what it offers.". Both are good options and you should use whatever offers what you want or need.

@shushtain
Comment options

If you are on nightly, then what are we even talking about? I'm glad you don't have the same issues, but they are still reproducible in v0.11.5, and I don't have enough skills to debug them on my own. As mentioned at the top of the post, the guide is mostly for v0.11.5.

The reason I encourage people to install servers and write formatting recipes on their own is because it demystifies LSP instead of hiding useful settings. For example,conform.nvim is very nice for care-free default recipes, but configuring it further requires studying its API that could be spent getting to know the formatter itself, and learning to run its commands as processes.

Debugging LSP requests, however, is not something I wish upon new users, especially since it could indeed be solved already in v0.12. So, again, I won't change my recommendation in this case, unless at least those mentioned issues are solved.

blink.cmp not only solves all the bugs I spotted, but also provides better docs,snippets/ folder support, cmd and terminal completion, ghost text suggestions, etc. I don't force anyone to use it, and I constantly test the native implementation to see if the problems are gone, because I also would like to switch.

@SebasF1349
Comment options

Sorry, I didn't know nightly users can't comment on github. Just wanted to comment one little thing. You said:

The reason I encourage people to install servers and write formatting recipes on their own is because it demystifies LSP instead of hiding useful settings.

And you shared a lengthy autocmd to show how to write that recipe. That's great! Sad that you consider then

Sure we can create a wrapper for each edge case with CompleteDonePre or by changing the core completefunc powering the logic. Sure we can add wrappers to source custom LSP-like snippets from a JSON file by parsing the latter. But that's why I didn't recommend creating your own linter, and that's why I don't recommend touching native completions for now.

when the snippets wrapper is literally 10 loc. Why writing your own recipe for formatting is worth but not for snippets?

Debugging LSP requests, however, is not something I wish upon new users

I have never had to debug any LSP request. If I have a bug, I report it, and let the people that knows to fix it.

Funny how I just said "builtin completion works with snippets, neovim has a builtin snippet engine." to end up being borbarded with problems with "docs, snippets/ folder support, cmd and terminal completion, ghost text suggestions", none of them go against what I previously stated which is objetively true. And funny enough none of that (except snippets/ folder support) has anything to do with snippets. But hey, it's your guide and I just wanted to give some input.

And I repeat: "Great guide".

@shushtain
Comment options

So, the course of events: I wrote that native snippet expansion is problematic (but in a nicer form). You replied that it works 100%. I showed you the issues. You said you can't reproduce themin a different version of the program. What should I do with that information? Thank you for your input, I guess. I spent some time reproducing all the mentioned issues, just to make sure I'm not that dumb...

I did make useful corrections based on your comments about "the engine". But you must understand that nightly and stable experiences are different. That's why I said that one day native completion will be better than anything we have so far, because it moves in the right direction. However, I can't ignore the existing issues, even if they are solved in nightly. I'm not writing docs for 10 years, I'm creating a snapshot of opinionated recommendations for specific version at this specific moment. Is that okay?

And I did explain why writing custom "format on save" is valuable. Because you learn tools you can use from anywhere. You learn to format JSON from shell after generating it in any program. The same with setting up all LSP config fields: you learn you can set up any LSP server, even ifnvim-lspconfig doesn't have a recipe.

When you write a wrapper to see docs, to gather snippets, to fix bugs with completion filtering, you still learn something. But those are editor-specific skills. Not all APIs are created equal. Feel free to make your own guide on writing a lean completion setup that covers the gaps.

Lastly, no matter how much you speculate about the length of my dotfiles,blink.cmp works OOTB with 1-3 lines of code required to install it. I'm sorry I had the audacity to set some custom icons in my personal config. How dare I...

I believe we are done talking about anything substantial. Goodbye.

Comment options

Nice setup!

If you are on nvim 0.11 or newer you no longer need to call

---"*" stands for "all LSPs"vim.lsp.config("*", {capabilities=require("blink.cmp").get_lsp_capabilities({},true),})

as blinkcalls it for you

You must be logged in to vote
1 reply
@shushtain
Comment options

Thanks! I didn't know that.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Labels
None yet
3 participants
@shushtain@antonk52@SebasF1349

[8]ページ先頭

©2009-2025 Movatter.jp