nvim-vtsls
April 27, 2025 ยท View on GitHub
Plugin to help utilize capabilities of vtsls.
NOTE: This plugin is not needed to work with vtsls. It simply offers some extra helper commands and optional improvements. Any server related issue should go up to the upstream.
Usage
Setup server
Through nvim-lspconfig:
require("lspconfig.configs").vtsls = require("vtsls").lspconfig -- set default server config, optional but recommended
-- If the lsp setup is taken over by other plugin, it is the same to call the counterpart setup function
require("lspconfig").vtsls.setup({ --[[ your custom server config here ]] })
Execute commands
:VtsExec {command}
Rename file/folder and update import paths
:VtsRename {from} {to}
Config
This is OPTIONAL. All the fields are also optional.
require('vtsls').config({
-- customize handlers for commands
handlers = {
source_definition = function(err, locations) end,
file_references = function(err, locations) end,
code_action = function(err, actions) end,
},
-- automatically trigger renaming of extracted symbol
refactor_auto_rename = true,
refactor_move_to_file = {
-- If dressing.nvim is installed, telescope will be used for selection prompt. Use this to customize
-- the opts for telescope picker.
telescope_opts = function(items, default) end,
}
})
Commands
| name | description |
|---|---|
restart_tsserver | This not restart vtsls itself, but restart the underlying tsserver. |
open_tsserver_log | It will open prompt if logging has not been enabled. |
reload_projects | |
select_ts_version | Select version of ts either from workspace or global. |
goto_project_config | Open tsconfig.json. |
goto_source_definition | Go to the source definition instead of typings. |
file_references | Show references of the current file. |
rename_file | Rename the current file and update all the related paths in the project. |
organize_imports | |
sort_imports | |
remove_unused_imports | |
fix_all | |
remove_unused | |
add_missing_imports | |
source_actions | Pick applicable source actions (same as above) |
API
require('vtsls').commands[any_command_name](bufnr, on_resolve, on_reject)
require('vtsls').commands.goto_source_definition(winnr, on_resolve, on_reject) -- goto_source_definition requires winnr
require('vtsls').rename(old_name, new_name, on_resolve, on_reject) -- rename file or folder
-- These callbacks are useful if you want to promisify the command functions to write async code.
function on_resolve() end -- after handler called
function on_reject(msg_or_err) end -- in case any error happens
Other useful snippets
Common settings to enable inlay hints
{
settings = {
typescript = {
inlayHints = {
parameterNames = { enabled = "literals" },
parameterTypes = { enabled = true },
variableTypes = { enabled = true },
propertyDeclarationTypes = { enabled = true },
functionLikeReturnTypes = { enabled = true },
enumMemberValues = { enabled = true },
}
},
}
}
Handler for codelens command
vim.lsp.commands["editor.action.showReferences"] = function(command, ctx)
local locations = command.arguments[3]
local client = vim.lsp.get_client_by_id(ctx.client_id)
if locations and #locations > 0 then
local items = vim.lsp.util.locations_to_items(locations, client.offset_encoding)
vim.fn.setloclist(0, {}, " ", { title = "References", items = items, context = ctx })
vim.api.nvim_command("lopen")
end
end
Then executing vim.lsp.codelens.run() will open up a quickfix window for references shown by the lens.
Integration to nvim-tree.lua for automatic renamed paths update
Excellent replacement for manually calling :VtsExec rename_file or :VtsRename.
You have two ways. You can use nvim-lsp-file-operations. It has integration with nvim and neo tree. Or you can use the following snippet. It also works for any server supporting workspace/didRenameFiles notification.
local path_sep = package.config:sub(1, 1)
local function trim_sep(path)
return path:gsub(path_sep .. "$", "")
end
local function uri_from_path(path)
return vim.uri_from_fname(trim_sep(path))
end
local function is_sub_path(path, folder)
path = trim_sep(path)
folder = trim_sep(folder)
if path == folder then
return true
else
return path:sub(1, #folder + 1) == folder .. path_sep
end
end
local function check_folders_contains(folders, path)
for _, folder in pairs(folders) do
if is_sub_path(path, folder.name) then
return true
end
end
return false
end
local function match_file_operation_filter(filter, name, type)
if filter.scheme and filter.scheme ~= "file" then
-- we do not support uri scheme other than file
return false
end
local pattern = filter.pattern
local matches = pattern.matches
if type ~= matches then
return false
end
local regex_str = vim.fn.glob2regpat(pattern.glob)
if vim.tbl_get(pattern, "options", "ignoreCase") then
regex_str = "\\c" .. regex_str
end
return vim.regex(regex_str):match_str(name) ~= nil
end
local api = require("nvim-tree.api")
api.events.subscribe(api.events.Event.NodeRenamed, function(data)
local stat = vim.loop.fs_stat(data.new_name)
if not stat then
return
end
local type = ({ file = "file", directory = "folder" })[stat.type]
local clients = vim.lsp.get_clients({})
for _, client in ipairs(clients) do
if check_folders_contains(client.workspace_folders, data.old_name) then
local filters = vim.tbl_get(client.server_capabilities, "workspace", "fileOperations", "didRename", "filters")
or {}
for _, filter in pairs(filters) do
if
match_file_operation_filter(filter, data.old_name, type)
and match_file_operation_filter(filter, data.new_name, type)
then
client:notify(
"workspace/didRenameFiles",
{ files = { { oldUri = uri_from_path(data.old_name), newUri = uri_from_path(data.new_name) } } }
)
end
end
end
end
end)