nvim-cmp icon indicating copy to clipboard operation
nvim-cmp copied to clipboard

Astro LSP completions sometimes write over incorrect ranges

Open Trildar opened this issue 1 year ago • 2 comments

FAQ

  • [X] I have checked the FAQ and it didn't resolve my problem.

Announcement

Minimal reproducible full config

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

require("lazy").setup({
	spec = {
		{
			"williamboman/mason.nvim",
			cmd = "Mason",
			keys = { { "<leader>cm", "<cmd>Mason<cr>", desc = "Mason" } },
			build = ":MasonUpdate",
			opts = {
				ensure_installed = {
					"astro-language-server",
				},
			},
			---@param opts MasonSettings | {ensure_installed: string[]}
			config = function(_, opts)
				require("mason").setup(opts)
				local mr = require("mason-registry")
				local function ensure_installed()
					for _, tool in ipairs(opts.ensure_installed) do
						local p = mr.get_package(tool)
						if not p:is_installed() then
							p:install()
						end
					end
				end
				if mr.refresh then
					mr.refresh(ensure_installed)
				else
					ensure_installed()
				end
			end,
		},
		"hrsh7th/cmp-nvim-lsp",
		{
			"hrsh7th/nvim-cmp",
			version = false, -- last release is way too old
			event = "InsertEnter",
			dependencies = {
				"hrsh7th/cmp-nvim-lsp",
			},
			opts = function()
				vim.api.nvim_set_hl(0, "CmpGhostText", { link = "Comment", default = true })
				local cmp = require("cmp")
				local defaults = require("cmp.config.default")()
				return {
					completion = {
						completeopt = "menu,menuone,noinsert",
					},
					mapping = cmp.mapping.preset.insert({
						["<C-n>"] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Insert }),
						["<C-p>"] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Insert }),
						["<C-b>"] = cmp.mapping.scroll_docs(-4),
						["<C-f>"] = cmp.mapping.scroll_docs(4),
						["<C-l>"] = cmp.mapping.complete(),
						["<C-e>"] = cmp.mapping.abort(),
						["<CR>"] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
						["<S-CR>"] = cmp.mapping.confirm({
							behavior = cmp.ConfirmBehavior.Replace,
							select = true,
						}), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
					}),
					sources = cmp.config.sources({
						{ name = "nvim_lsp" },
					}),
					experimental = {
						ghost_text = {
							hl_group = "CmpGhostText",
						},
					},
					sorting = defaults.sorting,
				}
			end,
		},
		"williamboman/mason-lspconfig.nvim",
		{
			"neovim/nvim-lspconfig",
			event = { "BufReadPre", "BufNewFile" },
			dependencies = {
				"mason.nvim",
				"williamboman/mason-lspconfig.nvim",
				"hrsh7th/cmp-nvim-lsp",
			},
			---@class PluginLspOpts
			opts = {
				-- LSP Server Settings
				---@type lspconfig.options
				servers = {
					astro = {},
				},
				setup = {},
			},
			---@param opts PluginLspOpts
			config = function(_, opts)
				local servers = opts.servers
				local has_cmp, cmp_nvim_lsp = pcall(require, "cmp_nvim_lsp")
				local capabilities = vim.tbl_deep_extend(
					"force",
					{},
					vim.lsp.protocol.make_client_capabilities(),
					has_cmp and cmp_nvim_lsp.default_capabilities() or {}
				)

				local function setup(server)
					local server_opts = vim.tbl_deep_extend("force", {
						capabilities = vim.deepcopy(capabilities),
					}, servers[server] or {})

					if opts.setup[server] then
						if opts.setup[server](server, server_opts) then
							return
						end
					elseif opts.setup["*"] then
						if opts.setup["*"](server, server_opts) then
							return
						end
					end
					require("lspconfig")[server].setup(server_opts)
				end

				-- get all the servers that are available through mason-lspconfig
				local have_mason, mlsp = pcall(require, "mason-lspconfig")
				local all_mslp_servers = {}
				if have_mason then
					all_mslp_servers = vim.tbl_keys(require("mason-lspconfig.mappings.server").lspconfig_to_package)
				end

				local ensure_installed = {} ---@type string[]
				for server, server_opts in pairs(servers) do
					if server_opts then
						server_opts = server_opts == true and {} or server_opts
						-- run manual setup if mason=false or if this is a server that cannot be installed with mason-lspconfig
						if server_opts.mason == false or not vim.tbl_contains(all_mslp_servers, server) then
							setup(server)
						else
							ensure_installed[#ensure_installed + 1] = server
						end
					end
				end

				if have_mason then
					mlsp.setup({ ensure_installed = ensure_installed, handlers = { setup } })
				end
			end,
		},
	},
	defaults = {
		-- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup.
		-- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default.
		lazy = false,
		-- It's recommended to leave version=false for now, since a lot the plugin that support versioning,
		-- have outdated releases, which may break your Neovim install.
		version = false, -- always use the latest git commit
		-- version = "*", -- try installing the latest stable version for plugins that support semver
	},
	performance = {
		rtp = {
			-- disable some rtp plugins
			disabled_plugins = {
				"gzip",
				-- "matchit",
				-- "matchparen",
				-- "netrwPlugin",
				"tarPlugin",
				"tohtml",
				"tutor",
				"zipPlugin",
			},
		},
	},
})

Description

Under some circumstances, inserting completions from the Astro LSP inside interpolations in the HTML template result in what looks like the completions being written over incorrect ranges in the buffer.

Steps to reproduce

Initialize an Astro project with npm create astro@latest or equivalent. Then try to ciw the test inside the {test} interpolations in a file like the below, type t, and insert a completion for the test variable.

---
const test = "TEST";
---
<!-- Case 1: Multiline tag -->
<div
    id={test}
>
</div>

<!-- Case 2: Nested interpolation -->
{<div id={test}></div>}

<!-- Case 3: Space before = -->
<div id ={test}></div>

Expected behavior

The completion gets inserted and the file is as given in the reproduction example.

Actual behavior

Individually, the cases will become as below. Note that doing the replacement and completions sequentially within the same file may not produce this due to the missing brackets changing the syntax.

---
const test = "TEST";
---
<!-- Case 1: Multiline tag -->
<div
    id={ttest
>
</div>

<!-- Case 2: Nested interpolation -->
{<div id={ttest

<!-- Case 3: Space before = -->
<div id =test}></div>

Additional context

Another user in the discussion linked below found no issue when using Neovim omnifunc to insert completions in similar circumstances.

Astro framework: https://astro.build/ Some prior discussion of this issue can be found here: https://github.com/LazyVim/LazyVim/discussions/1455

Trildar avatar Sep 16 '23 18:09 Trildar

Hi, @Trildar

I think this problem is a bug on the language server side. it does not cause problems with VSCode, but seems to cause problems with LSP clients of other editors. The start and end ranges for the selected completion items returned by the language server are probably incorrect. (completionItem/resolve)

You might want to report the issue to the language server side.

yaegassy avatar Oct 11 '23 06:10 yaegassy

Hi, @Trildar

I think this problem is a bug on the language server side. it does not cause problems with VSCode, but seems to cause problems with LSP clients of other editors. The start and end ranges for the selected completion items returned by the language server are probably incorrect. (completionItem/resolve)

You might want to report the issue to the language server side.

Yeah, looking at the language server responses, there does seem to be an issue with the ranges returned for completionItem/resolve, so I filed an issue here: https://github.com/withastro/language-tools/issues/664

But seeing as how Neovim omnifunc apparently also works fine (mentioned in the discussion on LazyVim repo), I have to wonder if it's just a matter of that and VS Code not using ranges from completionItem/resolve or some bigger issue with how nvim-cmp interacts with the language server.

Trildar avatar Oct 11 '23 19:10 Trildar

This issue already solved in latest @astrojs/language-server

hrsh7th avatar Mar 24 '24 08:03 hrsh7th