Hello, I had issues setting up neovim with vuejs plugins, so I am sharing the knowledge on how I got it to work.
This article assumes basic knowledge on how to add and modify your plugins with LazyVim.
I am using kickstart.nvim.
Packages that we will use : blink.cmp for autocompletion, typescript-tools for the typescript LSP, neovim/nvim-lspconfig to set up the LSP, and Mason so we can install volar.
1) Typescript-tools setup:
return {
'pmizio/typescript-tools.nvim',
dependencies = { 'nvim-lua/plenary.nvim', 'neovim/nvim-lspconfig' },
opts = {},
ft = { 'javascript', 'javascriptreact', 'typescript', 'typescriptreact', 'vue' },
config = function()
require('typescript-tools').setup {
on_attach = function(client, bufnr)
client.server_capabilities.documentFormattingProvider = false
client.server_capabilities.documentRangeFormattingProvider = false
end,
filetypes = {
'javascript',
'javascriptreact',
'typescript',
'typescriptreact',
'vue',
},
settings = {
tsserver_plugins = {
'@vue/typescript-plugin',
},
jsx_close_tag = {
enable = true,
filetypes = { 'javascriptreact', 'typescriptreact' },
},
},
}
end,
}
This is typescript-tools. It is tsserver, but faster.
In the tsserver_plugins table, we are calling @vue/typescript-plugin.
The recent versions of Volar, it embeds itself in the typescript LSP, providing it with capabilities.
We have to actually install this plugin.
Ideally on a per project basis, I installed it globally as of right now.
npm i -g @vue/typescript-plugin
2) LspConfig:
We are using neovim/nvim-lspconfig to set up the lsp functionalities.
return {
'neovim/nvim-lspconfig',
dependencies = {
{ 'williamboman/mason.nvim', config = true },
'williamboman/mason-lspconfig.nvim',
'WhoIsSethDaniel/mason-tool-installer.nvim',
{ 'j-hui/fidget.nvim', opts = {} },
'saghen/blink.cmp',
},
config = function()
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities = require('blink.cmp').get_lsp_capabilities(capabilities)
local servers = {
volar = { 'vue' },
lua_ls = {
settings = {
Lua = {
completion = { callSnippet = 'Replace' },
},
},
},
}
require('mason').setup()
local ensure_installed = vim.tbl_keys(servers)
-- vim.list_extend(ensure_installed, { 'stylua' })
require('mason-tool-installer').setup { ensure_installed = ensure_installed }
-- Setup mason-lspconfig
require('mason-lspconfig').setup {
ensure_installed = ensure_installed,
handlers = {
function(server_name)
local opts = servers[server_name] or {}
opts.capabilities = vim.tbl_deep_extend('force', {}, capabilities, opts.capabilities or {})
require('lspconfig')[server_name].setup(opts)
end,
},
}
-- Setup LspAttach autocmd for keymaps and additional functionality
vim.api.nvim_create_autocmd('LspAttach', {
group = vim.api.nvim_create_augroup('kickstart-lsp-attach', { clear = true }),
callback = function(event)
local map = function(keys, func, desc, mode)
mode = mode or 'n'
vim.keymap.set(mode, keys, func, { buffer = event.buf, desc = 'LSP: ' .. desc })
end
map('gd', require('telescope.builtin').lsp_definitions, '[G]oto [D]efinition')
map('gr', require('telescope.builtin').lsp_references, '[G]oto [R]eferences')
map('gI', require('telescope.builtin').lsp_implementations, '[G]oto [I]mplementation')
map('<leader>D', require('telescope.builtin').lsp_type_definitions, 'Type [D]efinition')
map('<leader>ds', require('telescope.builtin').lsp_document_symbols, '[D]ocument [S]ymbols')
map('<leader>ws', require('telescope.builtin').lsp_dynamic_workspace_symbols, '[W]orkspace [S]ymbols')
map('<leader>cr', vim.lsp.buf.rename, '[R]e[n]ame')
map('<leader>ca', vim.lsp.buf.code_action, '[C]ode [A]ction', { 'n', 'x' })
map('gD', vim.lsp.buf.declaration, '[G]oto [D]eclaration')
local client = vim.lsp.get_client_by_id(event.data.client_id)
if client and client.supports_method(vim.lsp.protocol.Methods.textDocument_documentHighlight) then
local highlight_group = vim.api.nvim_create_augroup('kickstart-lsp-highlight', { clear = false })
vim.api.nvim_create_autocmd({ 'CursorHold', 'CursorHoldI' },
{
buffer = event.buf,
group = highlight_group,
callback = vim.lsp.buf.document_highlight,
})
vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI'}, {
buffer = event.buf,
group = highlight_group,
callback = vim.lsp.buf.clear_references,
})
end
end,
})
end,
},
The most important thing here is that we are passing blink.cmp and that we are setting up volar as our LSP for .vue files.
Open up neovim and install volar.
:MasonInstall vue-language-server
Restart Neovim and this should be it. You should have all the functionalities, go to definition, go to reference, lsp errors etc.
Autocomplete config
If you have a functional autocomplete plugin, ignore this.
I am going to share my config for blink.cmp in case you need one.
return {
'saghen/blink.cmp',
-- dependencies = { 'rafamadriz/friendly-snippets', 'onsails/lspkind.nvim' },
dependencies = { 'onsails/lspkind.nvim' },
version = '*',
---@module 'blink.cmp'
---
---@type blink.cmp.Config
opts = {
keymap = {
['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' },
['<C-e>'] = { 'hide', 'fallback' },
['<CR>'] = { 'accept', 'fallback' },
['<Tab>'] = {
function(cmp)
return cmp.select_next()
end,
'snippet_forward',
'fallback',
},
['<S-Tab>'] = {
function(cmp)
return cmp.select_prev()
end,
'snippet_backward',
'fallback',
},
['<Up>'] = { 'select_prev', 'fallback' },
['<Down>'] = { 'select_next', 'fallback' },
['<C-p>'] = { 'select_prev', 'fallback' },
['<C-n>'] = { 'select_next', 'fallback' },
['<C-up>'] = { 'scroll_documentation_up', 'fallback' },
['<C-down>'] = { 'scroll_documentation_down', 'fallback' },
},
appearance = {
use_nvim_cmp_as_default = true,
nerd_font_variant = 'mono',
},
completion = {
accept = {
auto_brackets = {
enabled = true,
},
},
list = {
selection = function(ctx)
return ctx.mode == 'cmdline' and 'auto_insert' or 'preselect'
end,
},
--
menu = {
border = 'rounded',
cmdline_position = function()
if vim.g.ui_cmdline_pos ~= nil then
local pos = vim.g.ui_cmdline_pos -- (1, 0)-indexed
return { pos[1] - 1, pos[2] }
end
local height = (vim.o.cmdheight == 0) and 1 or vim.o.cmdheight
return { vim.o.lines - height, 0 }
end,
draw = {
columns = {
{ 'kind_icon', 'label', gap = 1 },
{ 'kind' },
},
components = {
kind_icon = {
text = function(item)
local kind = require('lspkind').symbol_map[item.kind] or ''
return kind .. ' '
end,
highlight = 'CmpItemKind',
},
label = {
text = function(item)
return item.label
end,
highlight = 'CmpItemAbbr',
},
kind = {
text = function(item)
return item.kind
end,
highlight = 'CmpItemKind',
},
},
},
},
},
sources = {
default = { 'lsp', 'path', 'buffer', 'snippets' },
cmdline = {},
providers = {
lsp = {
min_keyword_length = 2, -- Number of characters to trigger porvider
score_offset = 0, -- Boost/penalize the score of the items
},
path = {
min_keyword_length = 0,
},
snippets = {
min_keyword_length = 2,
},
buffer = {
min_keyword_length = 2,
},
},
},
},
},
Top comments (2)
This was actually really helpful. I usually use a CDN, but use Neovim for when I get tired of VS Code's UI. I Googled how to install Vue.js with Neovim, but all I got was a bunch of tutorials on how to use Vue. Actually, because Neovim is so complicated to use, I'll just use VS code with a CDN.
Wait, I can just use a CDN in Neovim too. :\
I also found very few resources on how to get vuejs to work. I was kinda surprised.
I love the clean UI of neovim and vim motions. Only 2 things that are not negotiable imo.