Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Use w/o lspconfig #167

Open
so-rose opened this issue Feb 10, 2025 · 4 comments
Open

[Feature Request] Use w/o lspconfig #167

so-rose opened this issue Feb 10, 2025 · 4 comments

Comments

@so-rose
Copy link

so-rose commented Feb 10, 2025

I'm putting together a config that uses the native neovim LSP configuration, without lspconfig (or mason). Since my life is primarily a stack of Quarto documents at this point, being able to use the handy functionality provided by quarto-nvim is close to my heart.

Unfortunately, as far as I know, it is impossible to use quarto-nvim without lspconfig, since one of the very first things quarto-nvim does is local util = require 'lspconfig.util'. As far as I can tell, the only reason this is done is to access the root directory: local root_dir = util.root_pattern '_quarto.yml'(buffer_path).

I was hoping you might consider removing the hard-dependency on lspconfig, by allowing the project root to be found by some other means (ex. a "you're on your own" config option where I provide my own root_dir finding logic).

Thank you for your time, and for quarto-nvim!
-Sof

What's Wrong with lspconfig?

Well, nothing. It's great! I refer to it frequently when configuring nvim. It is, however, opinionated. And its opinions differ from mine 😛 .

nvim's LSP system is itself not so opinionated (the slogan of nvim, perhaps). Perhaps not quite opinionated enough. As of recently, however, it seems that nvim can increasingly do more and more of the "really important" parts of lspconfig's job, like finding root folders based on markers and/or a custom function. It also seems that there's far less plumbing in today's nightly nvim. So I ask, why should I bring in in a massive plugin like lspconfig, which itself has a lot of nuanced functionality, for something nvim actually already does sufficiently?

My point is not so much to convince anybody, or claim "lspconfig is bad now". My point is there are valid reasons why it may not always desirable to use lspconfig, and that the flexibility of catering to this may be worth the effort!

But Isn't Non-lspconfig LSP Pretty Low Level?

Not really - though resources can be sparse, since lspconfig style configs dominates search engine results.

Suppose one wants ruff integration. Just uv tool install ruff, then create this auto-discovered file:

-- ~/.config/nvim/lsp/ruff.lua
return {
	cmd = { 'ruff', 'server' },
	filetypes = { 'python' },
	root_markers = { "pyproject.toml" },
}

Then, one just needs an LspAttach autocmd:

-- ~/.config/nvim/init.lua (or somewhere that makes you happy)
vim.api.nvim_create_autocmd('LspAttach', {
	callback = function(args)
            -- Retreive LSP Client & Buffer
            local lsp_client = vim.lsp.get_client_by_id(args.data.client_id)
            local buf = args.buf

            -- Implement LSP-Backed Commands/Keymaps/etc. .
            if lsp_client:supports_method(...) then
                ...
            end
        end
})

The official LSP docs helps fill in the blanks, including examples of how to wire up textDocument/formatting.

But Doesn't otter.nvim require lspconfig Too?

Officially, sure. As it turns out, when write_to_disk = true, the require('lspconfig') doesn't seem to be called. Apart from that, an autocmd on FileType that calls require('otter').activate() seems to transitively jiggle LspAttach just fine.

Here's my (seemingly) working lazy.nvim config:

return {
	'jmbuhr/otter.nvim',
	commit = "0e42fa795c35c7190935e3beda3791189c41bb72",
	lazy = true,

	init = function()
		vim.treesitter.language.register("markdown", { "quarto", "rmd" })

		vim.api.nvim_create_autocmd('FileType', {
			pattern = { 'quarto' },
			callback = function(ev)
				require('otter').activate()
			end
		})
	end,

	opts = {
		buffers = {
			set_filetype = true,
			write_to_disk = true,
		},
	},
}
@jmbuhr
Copy link
Collaborator

jmbuhr commented Feb 10, 2025

Agreed, I always like removing dependencies :)
And thank you for the well thought out issue.

I will just copy the the root dir function over into our own utils.

As far as otter is concerned:
When activating we currently gather the autocommands of the lspconfig group

local autocommands = api.nvim_get_autocmds({ group = "lspconfig", pattern = lang })

https://github.com/jmbuhr/otter.nvim/blob/0e42fa795c35c7190935e3beda3791189c41bb72/lua/otter/init.lua#L196

if one doesn't set the filetype, in order to activate just the language server and not other autocommands you might not need for the hidden otter buffer. However, that doesn't mean that the lspconfig autocmd group has to be created by nvim-lspconfig. That means, if you you put your own logic into an autocommand that is part of a lspconfig augroup that you create yourself, otter will execute those instead. I think I will put these instructions in a warning when the lspconfig augroup is not found (instead of erroring out).
I don't think we use nvim-lspconfig in other parts of otter (because most things are built directly into nvim now, like you said), so this alone should work.

@so-rose
Copy link
Author

so-rose commented Feb 10, 2025

Glad to hear it; just hope it can help!

So OK, in the past hour I got a local fork of quarto-nvim stripped of lspconfig, which is a two-line thing:

-- Delete 'local util = require 'lspconfig.util', of course
-- ...
  local root_dir = vim.fs.dirname(vim.fs.find({'_quarto.yml'}, { path = buffer_path, type = 'file', upward = true })[1])

This is more of a quick hack than anything, but seems to work well so far. It does presume that there's no such thing as a Quarto project that doesn't have a _quarto.yml (the rest is single-file projects started from cwd).

Maybe some kind of root_markers configuration entry for quarto-nvim could keep this general enough?

otter and Filetypes

I see the issue with wanting to run only the LSP-related autocmds. Hmm.

Perhaps, is there some way of manually telling nvim's native LSP system to pretend that a buffer is a filetype, just for attaching LSPs? Without actually setting the filetype of that buffer? For now the filetype setting doesn't seem to be much of an issue for me, but my config has also not yet exploded.

I can't immediately find any particular augroup / autocmds in the docs, so it may be a lower level trigger that the native system uses. But I have no idea. It's curious, at least.

Working Improved Config

So what I have here seems to work (with the set_filetype caveat aside) quite decently. I moved the otter activation logic to quarto-nvim's initialization method, since the logic is more a Quarto thing than a general "language-in-language" thing.

-- otter.nvim
return {
	'jmbuhr/otter.nvim',
	commit = "0e42fa795c35c7190935e3beda3791189c41bb72",
	lazy = true,

	opts = {
		buffers = {
			set_filetype = true,
			write_to_disk = true,  -- Some LSPs like this!
		},
	},
}
return {
	-- dir = local fork of quarto-nvim,
	lazy = true,

	cmd = {
		'QuartoPreview',
                -- Still gotta list out all the commands...
	},

        -- ...And add some keymaps...

	init = function()
		vim.treesitter.language.register("markdown", { "quarto", "rmd" })
		vim.api.nvim_create_autocmd('FileType', {
			pattern = { 'quarto' },
			callback = function(ev)
				require('quarto').activate()  -- Calls require('otter').activate() w/style
			end
		})
	end,

	opts = {
		codeRunner = {
			default_method = 'molten',
		},
	},
}

@jmbuhr
Copy link
Collaborator

jmbuhr commented Feb 10, 2025

yeah, I think the filetype setting generally just works and wanting actual files on disk tends be the case with some linters (your ruff).

I don't think you need vim.treesitter.language.register("markdown", { "quarto", "rmd" }) anymore, because we have that in quarto-nvim (https://github.com/quarto-dev/quarto-nvim/blob/main/ftplugin/quarto.lua).
(ah, now I have the full context, you lazy load it only at the commands, maybe just don't do that)

Still gotta list out all the commands...

This raises the question if there is some standard way lazy.nvim expects usercommands to be exposes such that it can autodiscover, even though for quarto-nvim this shouldn't be required, because most people will just load it on ft={'quarto'}. Still an interesting question.

@jmbuhr
Copy link
Collaborator

jmbuhr commented Feb 10, 2025

the augroup is here: https://github.com/neovim/nvim-lspconfig/blob/d37812c49063eda10ad1f7c7695509365ac7bd47/lua/lspconfig/configs.lua#L63

But like I said, you can create your own augroup that happens to have the same name and do your lsp autoloading in there, then otter also picks it up. Or just keep setting the ft.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants