If you’re like me, you prefer Vue over React, Neovim over VSCode, and you have a love-hate relationship with TypeScript. The pain you must go through to have Neovim work efficiently with Vue 3 and TypeScript isn’t helping either.

I’ll spare any long-winded tale about how much hang-banging there was to make it all work. Long story short, the Volar 2 Vue language server no longer has TypeScript support built in. So, with it, you have to use the ts_ls TypeScript LSP, a.k.a. typescript-language-server. (Yeah, the names and aliases throw me off too.)

Let’s just jump into the Neovim config I had to put it to have them play together peacefully, marching together hand in hand towards peace.

Contents

The LSP config

Keep in mind, I forked an NvChad starter config with which does some things automatically, like loading mappings and pre-made LSP configs.

⚠️ : Heads up! Two months ago, require('lspconfig') was deprecated in favour of vim.lsp.config() and vim.lsp.enable() in Nvim 0.11. The code below was for the earlier. To see an example usage of the new API, see the current version of this file.

-- ~/.config/nvim/lua/configs/lspconfig.lua
-- Loaded automatically by NvChad

-- Load defaults i.e lua_lsp
require("nvchad.configs.lspconfig").defaults()

local lspconfig = require "lspconfig"
local nvlsp = require "nvchad.configs.lspconfig"

-- Dynamically point to the path of @vue/language-server
-- which contains @vue/typescript-plugin
local vue_typescript_plugin =
	vim.fn.expand(vim.fn.stdpath "data" .. "/mason/packages/vue-language-server/node_modules/@vue/language-server")

-- Set up ts_ls LSP with @vue/typescript-plugin
lspconfig.ts_ls.setup {
	on_attach = nvlsp.on_attach,
	on_init = nvlsp.on_init,
	capabilities = nvlsp.capabilities,
	init_options = {
		plugins = {
			{
				name = "@vue/typescript-plugin",
				location = vue_typescript_plugin,
				languages = { "vue" },
			},
		},
	},
	filetypes = {
		"javascript",
		"typescript",
		"vue",
	},
	settings = {
		typescript = {
			tsserver = {
				useSyntaxServer = false,
			},
			inlayHints = {
				includeInlayParameterNameHints = "all",
				includeInlayParameterNameHintsWhenArgumentMatchesName = true,
				includeInlayFunctionParameterTypeHints = true,
				includeInlayVariableTypeHints = true,
				includeInlayVariableTypeHintsWhenTypeMatchesName = true,
				includeInlayPropertyDeclarationTypeHints = true,
				includeInlayFunctionLikeReturnTypeHints = true,
				includeInlayEnumMemberValueHints = true,
			},
		},
	},
}

The config above was inspired by the official nvim-lspconfig docs by Neovim and this article by Dan Walsh.

Showing messages

By default, diagnostic messages about your code will appear as a single line, which may get too long to read.

To have them appear in a floating box which only show up when your cursor is on the offending line, you can try adding the following in lspconfig.lua:

vim.diagnostic.config {
	virtual_text = false, -- Show text after diagnostics
	signs = true,
	update_in_insert = false,
	underline = true,
	severity_sort = false,
	float = true,
}

-- Show diagnostics text on cursor hold
local lspGroup = vim.api.nvim_create_augroup("Lsp", { clear = true })

vim.api.nvim_create_autocmd("CursorHold", {
	command = "lua vim.diagnostic.open_float()",
	group = lspGroup,
})

Thanks to Naborisk for providing the two parts for the above. They’re another avid user of Neovim and requested not to be asked how much time was needed to figure that out. (Us Neovim users can all understand the embarassment of spending days on end tweaking our config.)

Complete configuration

To view the entire setup, you can also view the complete lspconfig.lua file as of this article and its latest version.

Now, go ahead, and be happy go lucky with all your Vue 3 and TypeScript action.

👋 Hi! I'm Rem, a Web developer since 1998, in Japan since 2006. I'm the author of dress.css and Scrollerful. As you can imagine, my experience is varied, including the early days of the Web being a webmaster writing HTML files and scripts in Perl, saved on floppy disks using Notepad, to now being a tech lead for a team, writing CSS with Tailwind or JavaScript for both the backend and frontend in Neovim. I love photography, typography, and colour theory. Strangely enough, my love for print graphic design was what got me into web development. So, yes, I have a few stories to tell and tips to give, and I'm writing some of them, sometimes, here, on RÉMINO Bits.