Go to references doesn't not find references in other libs if not visited before
I have a Nx repository, with basically apps/* and libs/*. When I FindReferences it only shows references if I have previously visited the app or library that reference the expression.
LSP Info:
Client: typescript-tools (id: 3, bufnr: [6])
filetypes: javascript, javascriptreact, javascript.jsx, typescript, typescriptreact, typescript.tsx
autostart: true
root directory: /home/romain/workspace/monorepo
cmd: <function>
Other clients that match the filetype: typescript
Setup
require("typescript-tools").setup {
on_init = on_init,
on_attach = on_attach,
capabilities = capabilities,
-- NOTE: i read somewhere that this could help
root_dir = util.root_pattern ".git",
}
Is it out of the scope of this plugin to support such a monorepo setup? If yes, could you please, lead me to a potential solution based on your expertise? (I'm not familiar with this)
Related resources:
@IzioDev I looked into resources you provided and they say you need this @monodon/typescript-nx-imports-plugin plugin loaded into service. We support tsserver plugins, you can try to:
npm i -g @monodon/typescript-nx-imports-plugin
Notice -g to install plugin globally.
Then:
require("typescript-tools").setup {
on_init = on_init,
on_attach = on_attach,
capabilities = capabilities,
-- NOTE: i read somewhere that this could help
root_dir = util.root_pattern ".git",
settings = {
tsserver_plugins = { "@monodon/typescript-nx-imports-plugin" },
},
}
You probably need some configuration in your tsconfig.json.
If this won't help, so I think it is out of scope of this plugin, vscode has also separate plugin to support nx monorepos.
I think here it should be similar.
Hey @pmizio, thanks for the quick answer.
Yes it should be one part of the issue here. To summarize what VSCode or coc (port of the VSCode one) extensions are doing:
- on loads it verifies that
typescriptextension (LSP) is running - register 2 watchers
a. on text document opened: it call
configurePluginb. on tsconfig base (monorepo global tsconfig): flush cache andconfigurePlugin - it calls
configurePlugin(initiation)
configurePlugin, is does this thing:
api.configurePlugin('@monodon/typescript-nx-imports-plugin', {
externalFiles,
});
that seems to loads all 'related' project so that LSP server knows about them in the future, hence making Goto:Reference and import cross-project working out-of-the box for monorepo configuration.
analysis based on the nx coc extension
So by 'only' registering the plugin, I'm afraid it's missing a key piece here.
What i have in mind currently, that would be re-usable by anyone:
- create a neovim plugin, that listen to workspace changes and mimic what's
coc-nxis doing
I just need one extra help from you, since I'm a newbie:
-
here they get an 'API' to the
typescriptplugin - is ittypescript-toolsthat would be involved in this case? If that's so, is there an API that we (as plugin developer) could use as such? (it needs some parameters) -
the underlying typescript extension vscode on plugin configuration change
@IzioDev Ok, so on typescript-tools side we should implement this request to mimic api.configurePlugin call. It will probably look very similar to this, then you can call vim.lsp.buf_request("typescriptTools/configurePlugin", {...params}). I probably can implement this request after monday, so if you in rush you can try to do it yourself, it isn't complicated. You probably won't need to setup custom watcher for document because we expose User autocommand which fire when nvim notify tsserver about open/change/close file. You probably will need to setup watcher for tsconfig.
Let me know if this explanation is helful. I think everything I described should let you implement nx support.
Awesome, yes I started writing the command. Thanks for all the explanation, will submit a PR soon if I succeed
There is something I do wrongly PR HERE
Using:
:lua vim.lsp.buf_request(0, "typescriptTools/configurePlugin", { pluginName = "test", configuration = {}})
give me handler not found
@IzioDev And that's true, we don't have response handler for this request and I don't think we need it because this request has no response at least vscode don't use it. So to bypass this error you need to pass handler callback to buf_request. So I think it is last param so vim.lsp.buf_request(0, "...", {...}, function()end) should do the trick. But first verify where callback really is because I don't remember it - :help vim.lsp.buf_request.
And about pr, like I said this request don't have response or we can skip it just like vscode, so you don't need to coroutine.yield() and response it.
After some tests, it seems not to work when invoking directly via vim.lsp.bug_req*, I have an error request cannot be invoked, nil value which is weird, since it's a 'common' request handler, no?
I tried with client.request but it seems to do nothing:
local test = require("typescript-tools.protocol.text_document.configure_plugin")
local clients = vim.lsp.get_active_clients()
for _, client in ipairs(clients) do
if client.name == "typescript-tools" then
client.request("typescriptTools/configurePlugin", {
pluginName = "@monodon/typescript-nx-imports-plugin",
configuration = {
externalFiles = {
{
mainFile = "/home/izio/workspace/angular/apps/frontend/src/main.ts",
directory = "/home/izio/workspace/angular/apps/frontend",
},
{
mainFile = "/home/izio/workspace/angular/libs/test1/src/index.ts",
directory = "/home/izio/workspace/angular/libs/test1",
},
{
mainFile = "/home/izio/workspace/angular/libs/test2/src/index.ts",
directory = "/home/izio/workspace/angular/libs/test2",
},
},
},
}, test.handler, 0)
end
end
-- vim.lsp.buf_request(0, "typescriptTools/configurePlugin", { pluginName = "test", configuration = {} }, test.handler)
You can set this value in config to verbose then in your os temp directory tsserver start dump it's logs. You can check tsserver_semantic|syntax.log to inspect is even this new method is send to tsserver.
About this error if you provide stacktrace, it will be easier for me to potentially hint something useful :) As last resort I just checkout your pr in Monday and maybe I find why this error occurr.
Ooooh I see it now... Sorry I write from phone and I cannot see everything. But you pass server side handler to client side response, I know this is a bit confusing but you literally need to call:
vim.lsp.buf_request(0, "typescriptTools/configurePlugin", { pluginName = "test", configuration = {} }, function()end)
This handler in nvim lsp model should process and invoke client side things. But in here we can call if with empty function because configurePlugin has no response.
Your current implementation call "server side"/internal handler which operate on raw tsserver I/O. So you get nil because handler is called without context.
I hope I wrote this more clearly, let me know if I can explain this better 😉
It's all crystal clear, thanks for the explanation! I take this back
Thanks again for your efforts
I made it work, it's raw currently, polishing that, and hopefully able to add this watcher thing etc... so that it's done on a per-project basis (no global static configuration needed)
Question: Is it possible to enable this flag on the tsserver : allowLocalPluginLoads, if I understand well, it adds the project directory to the list of seacheable plugin paths, which would allow two things:
- plugin is always using workspace verison (not a global system installation)
- no need to install a global system plugin It may cause a breaking change, because it is not clear what priority folders have between them.
While researching this flag you mentioned I found this issue and based on it you can use currently supported flag --pluginProbeLocation, here you need to add second path. I think they are comma separated.
It seems like the autogroup is created in a 'lazy' way. Creating an auto-command from another plugin (even if setup after typescript-tools) gives the following output:
Failed to run `config` for typescript-tools-nx.nvim
...ypescript-tools-nx.nvim/lua/typescript-tools-nx/init.lua:6: Invalid 'group': 'TypescriptToolsGroup'
# stacktrace:
- ~/projects/typescript-tools-nx.nvim/lua/typescript-tools-nx/init.lua:6 _in_ **setup**
- ~/.config/nvim/lua/custom/configs/lspconfig.lua:88
- ~/.config/nvim/lua/custom/plugins.lua:30 _in_ **config**
- ~/.config/nvim/lua/core/utils.lua:104
Is there any way to 'wait' for the group to be created? Or should I register my autocmd anyhow else?
local constants = require("typescript-tools.protocol.constants")
function M.setup()
vim.api.nvim_create_autocmd({ "TypescriptTools_" .. constants.LspMethods.DidOpen }, {
pattern = { "*.ts" },
-- ...
group = "TypescriptToolsGroup",
})
end
Here you have example how to setup callback to this autocommand correctly. Here is function which create autogroup.
Autogroups should be unique per plugin so you should make up your own.
Hello guys, I am the one that implemented the coc-nx port. I am using nx and I want to get into neovim, but this simple (as far as I can understand) issue is holding me back for almost a year now! I would be glad to see this implemented and I am watching this issue closely! Thanks!
Hello @AkisArou, I wont be able to make progression on this soon, but there is a pending MR that send a configurePlugin command to tsserver, from typescript-tools that could be almost self-sufficient for most needs.
One could improve this, and expose it as part of the api (instead of manually build the lsp client request etc...)
As an almost total newbie to nvim editor and lua, I will wait for it to be ready and check someone elses nvim config! Thanks!
FYI: just merged(https://github.com/pmizio/typescript-tools.nvim/pull/167) configurePlugin request into master.
I made it work, it's raw currently, polishing that, and hopefully able to add this watcher thing etc... so that it's done on a per-project basis (no global static configuration needed)
Question: Is it possible to enable this flag on the
tsserver:allowLocalPluginLoads, if I understand well, it adds the project directory to the list of seacheable plugin paths, which would allow two things:
- plugin is always using workspace verison (not a global system installation)
- no need to install a global system plugin It may cause a breaking change, because it is not clear what priority folders have between them.
Any chance you can show how you got this working? This is my current attempt (not working)...
require('typescript-tools').setup(u.merge(defaults, {
tsserver_plugins = {
'@styled/typescript-styled-plugin',
'@monodon/typescript-nx-imports-plugin',
},
on_attach = function(client, bufnr)
defaults.on_attach(client, bufnr)
vim.lsp.buf_request(bufnr, 'typescriptTools/configurePlugin', {
pluginName = '@monodon/typescript-nx-imports-plugin',
configuration = {
externalFiles = {
{
mainFile = '/Users/{user}/{repo}/{path_to_lib}/src/index.ts',
directory = '/Users/{user}/{repo}/{path_to_lib}',
},
-- { ... }
},
},
}, function() end)
end,
}))
@mattleong
I think I am getting close to a solution. For now I managed to make it somewhat work with the typescript-language-server plugin like so:
lspconfig.tsserver.setup {
on_attach = function(client, bufnr)
-- call the base on_attach
on_attach(client, bufnr)
-- configure plugin
local method = "_typescript.configurePlugin"
client.request("workspace/executeCommand", {
command = method,
arguments = {
"@monodon/typescript-nx-imports-plugin",
{
externalFiles = {
{
mainFile = "C:/.../{repo}/apps/{app}/src/main.ts",
directory = "C:/.../{repo}/apps/absint-app",
},
},
},
},
})
end,
capabilities = capabilities,
root_dir = lspconfig.util.root_pattern ".git",
-- cmd = { "typescript-language-server", "--stdio", "--log-level", "4" },
init_options = {
hostInfo = "neovim",
plugins = {
{
name = "@monodon/typescript-nx-imports-plugin",
location = "C:\\Users\\{user}\\AppData\\Roaming\\nvm\\v18.10.0",
},
},
tsserver = {
logVerbosity = "verbose",
},
},
}
For some reason it only works in the on_attach callback and not in the on_innit one. Then it loads it on a first try, but dissapears quickly. Reopening the buffer makes it stick tho. See in action: https://streamable.com/af9ydy
I basically have very little idea what I am doing, I am new to neovim and lua all together... But I am sure it can be fixed and also make work with this beautiful plugin rather than the typescript-language-server one.
@mattleong [...] not in the
on_innitone. [...]
I'm sure you didn't actually code in Londoner but worth checking that you tried on_init! Your setup looks nice btw, thanks for the config so far.
I am now trying to make this work with this plugin, but I can't get the workspace/executeCommand to work. @pmizio do you have an idea what I might have done wrong? Is it necessary to call it like so - can't i just request the configurePlugin directly?
require("typescript-tools").setup {
on_attach = function(client, bufnr)
on_attach(client, bufnr)
print "typescript-tools on attach"
local method = "typescriptTools/configurePlugin"
local result = client.request("workspace/executeCommand", {
command = method,
arguments = {
pluginName = "@monodon/typescript-nx-imports-plugin",
configuration = {
externalFiles = {
{
mainFile = "C:/Projects/datera/web-applications/apps/absint-app/src/main.ts",
directory = "C:/Projects/datera/web-applications/apps/absint-app",
},
},
},
},
}, function()
print "EXECUTED WORKSPACE COMMAND"
end, bufnr)
print("exec cmd res", result)
end,
capabilities = capabilities,
root_dir = lspconfig.util.root_pattern ".git",
settings = {
tsserver_plugins = {
"@monodon/typescript-nx-imports-plugin",
},
tsserver_logs = "verbose",
},
}
Using the typescript-language-server I get the response from tsserver (EXECUTED WORKSPACE COMMAND log appears). But with typescript-tools.nvim the handler is not called. The result of the client.request is true however.
EDIT:
no matter what I try, I cannot get the request to configure plugins to the TSServer with typescript-nvim.tools. I thought it might be concurrency issue by trying to call it on the on_attach or on_init, so I am trying to call it from within neovim like so:
:lua vim.lsp.get_client_by_id(1).request("typescriptTools/configurePlugin", {}, function()end)
but there is no trace of it inside the syntax nor the semantic tsserver log.
@faileon I think workspace/executeCommand is typescript-language-server way of doing things. In this plugin it isn't even implemented. Here it is done with custom protocol extension typescriptTools/configurePlugin.
Something like below should do the trick:
...
local result = client.request("typescriptTools/configurePlugin", {
pluginName = "@monodon/typescript-nx-imports-plugin",
configuration = {
externalFiles = {
{
mainFile = "C:/Projects/datera/web-applications/apps/absint-app/src/main.ts",
directory = "C:/Projects/datera/web-applications/apps/absint-app",
},
},
},
}, function()
print "EXECUTED WORKSPACE COMMAND"
end, bufnr)
...
EDIT: You may try to call this after didOpen autocommand fired from this extension(example) if it is case of concurrency after this autocommand file should be fully ready to work with.
@pmizio I am now just calling it from the neovim command line and tracing where the configurePlugin gets lost and I arrived here:
https://github.com/pmizio/typescript-tools.nvim/blob/master/lua/typescript-tools/tsserver.lua#L129
which yields in module 'typescript-tools.protocol.text_document.configure_plugin' not found:
I think at this point it might by my botched up windows. I will try tomorrow on my linux machine...
You right I also spotted this bug, fix here: https://github.com/pmizio/typescript-tools.nvim/pull/222, let me know if it helps if yes I'll merge it.
You right I also spotted this bug, fix here: #222, let me know if it helps if yes I'll merge it.
Yay! This has unclogged the pipes and my requests are now arriving in the TSServer:
Info 2109 [23:32:48.823] request: {"seq":12,"type":"request","command":"configurePlugin","arguments":[]}
Ok for anyone interested, so far this seems to somewhat work:
local constants = require "typescript-tools.protocol.constants"
local method = constants.CustomMethods.ConfigurePlugin
local args = {
pluginName = "@monodon/typescript-nx-imports-plugin",
configuration = {
externalFiles = {
{
mainFile = "C:/.../{repo}/apps/{app}/src/main.ts",
directory = "C:/.../{repo}/apps/absint-app",
},
-- ...other projects (apps, libs)
},
},
}
require("typescript-tools").setup {
on_init = function(client, init_result)
-- tsserver receives it, but does not work
client.request(method, args)
end,
on_attach = on_attach,
capabilities = capabilities,
root_dir = lspconfig.util.root_pattern ".git",
settings = {
tsserver_plugins = {
"@monodon/typescript-nx-imports-plugin",
},
tsserver_logs = "verbose",
},
}
local augroup = vim.api.nvim_create_augroup("NxGroup", { clear = true })
vim.api.nvim_create_autocmd("User", {
pattern = {
"TypescriptTools_" .. constants.LspMethods.DidOpen,
},
callback = function(event)
-- print("DidOpen", vim.inspect(event))
-- TODO: only send it to typescript-tools client?
-- local clients = vim.lsp.get_active_clients()
vim.lsp.buf_request(0, method, args, function()
print("tsserver handled", method)
end)
end,
group = augroup,
})
This works just like with typescript-language-server with the same hiccups:
- suggestions appear, then disappear quickly
- reopening the buffer OR restarting typescript-tools.nvim with
:LspRestartmakes the suggestions stick
It still does not work when called in the on_init callback. The request is received byt TSServer, but maybe it is too soon and the monodon plugin is not yet instantiated in the TSServer? If this would work, the easiest naive approach would be configuring the plugin here, feeding it all the projects found in the workspace. Adding new app/lib would require restart, but I could live with that.
Calling it in the autocommand DidOpen makes it work, but will send it everytime you open a buffer. Seems like an overkill?
Regarding retrieving all projects in the workspace, I am not sure how to handle it. I don't think reading tsconfig.base.json is enough, because that only contains libraries and not apps. We could instead search for all project.json files and use the sourceRoot property to create the configuration object for monodon plugin.
Alright, I have a solution... It's not a super polished one, but it works well for me now and I can let it rest.
Setup the lsp:
require("typescript-tools").setup {
on_init = function(client, bufnr)
vim.schedule(function()
vim.cmd.NxInit()
end)
end,
on_attach = on_attach,
capabilities = capabilities,
root_dir = lspconfig.util.root_pattern ".git",
settings = {
tsserver_plugins = {
"@monodon/typescript-nx-imports-plugin",
}
},
}
In your init.lua create this command of a monstrosity
vim.api.nvim_create_user_command("NxInit", function()
print "Running nx init..."
-- call nx deamon with graph request
local now = tostring(os.time())
local fileName = string.format("%s_graph.json", now)
local cmd = string.format("npx nx graph --file=%s", fileName)
os.execute(cmd)
-- read the file into memory
local projectGraphFile = io.open(fileName, "r")
if projectGraphFile then
-- read project graph from file
local projectGraph = vim.json.decode(projectGraphFile:read "*a")
projectGraphFile:close()
-- get typescript-tools lsp client
local lspClients = vim.lsp.get_active_clients()
local tsclient
for _, client in ipairs(lspClients) do
if client.name == "typescript-tools" then
tsclient = client
end
end
if not tsclient then
print "typescript-tools.nvim not active"
return
end
-- get workspace root
-- always first? alternatives to get absolute workspace root?
local workspacePath = tsclient.config.workspace_folders[1].name
if not workspacePath then
print "Could not figure out workspace path"
return
end
-- create external files for monodon
local externalFiles = {}
for _, project in pairs(projectGraph.graph.nodes) do
local sourceRoot = project.data.sourceRoot
-- skip the root
if sourceRoot ~= "." then
-- localte the entry file. perhaps use tsconfig.[app|lib].json
local mainFile
if
project.data
and project.data.targets
and project.data.targets.build
and project.data.targets.build.options
and project.data.targets.build.options.main
then
mainFile = workspacePath .. "/" .. project.data.targets.build.options.main
else
mainFile = workspacePath .. "/" .. sourceRoot .. "/index.ts"
end
-- insert to config
table.insert(externalFiles, {
mainFile = mainFile, -- this is not always index.ts!
directory = workspacePath .. "/" .. sourceRoot,
})
end
end
-- send configuration request of monodon plugin to tsserver
local constants = require "typescript-tools.protocol.constants"
local method = constants.CustomMethods.ConfigurePlugin
local args = {
pluginName = "@monodon/typescript-nx-imports-plugin",
configuration = {
externalFiles = externalFiles,
},
}
tsclient.request(method, args, function()
print("tsserver handled configuration request", method)
end)
-- remove the graph file
os.remove(fileName)
end
end, {})
It will be called once on initialization of typescript-tools.nvim, or you can call it on demand with :NxInit. It does the following steps:
- Asks nx daemon for project graph via
npx nx graph- you must be in the correct directory (the git root) for this to work. - Creates a file with the project graph, reads it, parses it and creates the Monodon plugin configuration. There is some attempt to distinguish between
index.tsandmain.tsentry points, but you might need to adjust it based on your use case. - Sends the configuration to tsserver
- deletes the project graph file so it doesnt polute your file system
Demonstration: https://streamable.com/ivzwy1
@faileon Should tsclient.request recieve a buffer number? I'm getting a type error saying it needs a 4th parameter. It seems fine either way since eventually the nil value eventually gets handled but just double checking.
tsclient.request(method, args, function()
print("tsserver handled configuration request", method)
end, vim.api.nvim_get_current_buf())
Also, I'm still having trouble getting it working. The project graph file builds and everything but I'm not seeing any differences. I'll let you know if I can figure out what's missing on my end.
@faileon Should
tsclient.requestrecieve a buffer number? I'm getting a type error saying it needs a 4th parameter. It seems fine either way since eventually thenilvalue eventually gets handled but just double checking.tsclient.request(method, args, function() print("tsserver handled configuration request", method) end, vim.api.nvim_get_current_buf())Also, I'm still having trouble getting it working. The project graph file builds and everything but I'm not seeing any differences. I'll let you know if I can figure out what's missing on my end.
I have tried both with and without buffer, it didn't seem to make a difference. It is just configuring the tsserver plugin afterall.
I have updated the script a little bit, but the changes I made are just optimization to run in background. Some of my monorepos are quite big and it used to freeze my UI for up to 5-10s without it. You can find the updated code here https://github.com/faileon/nvchad/blob/0f94afac075b786970541aee85d4a7a1447a26b4/lua/custom/init.lua#L23
I think the first time you will hit GR it still takes some time for the references to load up, but otherwise it works OK on my side.
Try to check the tsserver logs and see what kind of configuration gets send to the tsserver for this plugin. Perhaps the paths are wrong for some of the projects in your case.